Skip to content

Commit

Permalink
Merge pull request #1061 from alvasw/tor_implement_bootstrap_event_ha…
Browse files Browse the repository at this point in the history
…ndler_and_listeners

Tor: Implement bootstrap event handler and listener
  • Loading branch information
alvasw authored Jul 31, 2023
2 parents 4d0ca74 + 1358fe9 commit f5ebc91
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 1 deletion.
87 changes: 87 additions & 0 deletions network/tor/src/main/java/bisq/tor/bootstrap/BootstrapEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.tor.bootstrap;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

@Builder
@Getter
@ToString
public class BootstrapEvent {

private static final String DONE_TAG = "done";

private final int progress;
private final String tag;
private final String summary;

public BootstrapEvent(int progress, String tag, String summary) {
if (progress < 0 || tag.isEmpty() || summary.isEmpty()) {
throw new IllegalArgumentException("Invalid bootstrap event: " + progress + " " + tag + " " + summary);
}

this.progress = progress;
this.tag = tag;
this.summary = summary;
}

public boolean isDoneEvent() {
return tag.equals(DONE_TAG);
}

public static boolean isBootstrapMessage(String type, String message) {
return type.equals("STATUS_CLIENT") && message.contains("BOOTSTRAP");
}

public static BootstrapEvent fromEventMessage(String message) {
String[] keyValuePairs = message.split(" ");

int progress = -1;
String tag = "";
String summary = "";
for (String item : keyValuePairs) {
if (!item.contains("=")) {
continue;
}

String[] parts = item.split("=");
if (parts.length != 2) {
throw new IllegalStateException("Tor event key value pair has more than two '=' signs.");
}

String key = parts[0];
String value = parts[1];

switch (key) {
case "PROGRESS":
progress = Integer.parseInt(value);
break;
case "TAG":
tag = value;
break;
case "SUMMARY":
summary = value;
break;
}
}

return new BootstrapEvent(progress, tag, summary);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.tor.bootstrap;

import net.freehaven.tor.control.EventHandler;

import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class BootstrapEventHandler implements EventHandler {

private final Set<BootstrapEventListener> listeners = new CopyOnWriteArraySet<>();

public void addListener(BootstrapEventListener listener) {
listeners.add(listener);
}

public void removeListener(BootstrapEventListener listener) {
listeners.remove(listener);
}

@Override
public void circuitStatus(String status, String circID, String path) {
}

@Override
public void streamStatus(String status, String streamID, String target) {
}

@Override
public void orConnStatus(String status, String orName) {
}

@Override
public void bandwidthUsed(long read, long written) {
}

@Override
public void newDescriptors(List<String> orList) {
}

@Override
public void message(String severity, String msg) {
}

@Override
public void hiddenServiceEvent(String action, String msg) {
}

@Override
public void hiddenServiceFailedEvent(String reason, String msg) {
}

@Override
public void hiddenServiceDescriptor(String descriptorId, String descriptor, String msg) {
}

@Override
public void unrecognized(String type, String msg) {
if (BootstrapEvent.isBootstrapMessage(type, msg)) {
BootstrapEvent bootstrapEvent = BootstrapEvent.fromEventMessage(msg);
listeners.forEach(l -> l.onBootstrapStatusEvent(bootstrapEvent));
}
}

@Override
public void timeout() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.tor.bootstrap;

public interface BootstrapEventListener {
void onBootstrapStatusEvent(BootstrapEvent bootstrapEvent);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.tor.bootstrap;

public class TorBootstrapFailed extends RuntimeException {
public TorBootstrapFailed(String message) {
super(message);
}

public TorBootstrapFailed(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,27 @@
package bisq.tor.process;

import bisq.tor.ClientTorrcGenerator;
import bisq.tor.bootstrap.BootstrapEvent;
import bisq.tor.bootstrap.BootstrapEventHandler;
import bisq.tor.bootstrap.BootstrapEventListener;
import bisq.tor.bootstrap.TorBootstrapFailed;
import lombok.extern.slf4j.Slf4j;
import net.freehaven.tor.control.PasswordDigest;
import net.freehaven.tor.control.TorControlConnection;

import java.io.IOException;
import java.net.Socket;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class NativeTorController {
@Slf4j
public class NativeTorController implements BootstrapEventListener {

private final CountDownLatch isBootstrappedCountdownLatch = new CountDownLatch(1);
private final BootstrapEventHandler bootstrapEventHandler = new BootstrapEventHandler();
private Optional<TorControlConnection> torControlConnection = Optional.empty();

public void connect(int controlPort, PasswordDigest controlConnectionSecret) throws IOException {
Expand All @@ -45,11 +57,50 @@ public void bindTorToConnection() throws IOException {

public void enableTorNetworking() throws IOException {
TorControlConnection controlConnection = torControlConnection.orElseThrow();
addBootstrapEventListener(controlConnection);
controlConnection.setConf(ClientTorrcGenerator.DISABLE_NETWORK_CONFIG_KEY, "0");
}

public void waitUntilBootstrapped() {
try {
boolean isSuccess = isBootstrappedCountdownLatch.await(2, TimeUnit.MINUTES);
if (!isSuccess) {
throw new TorBootstrapFailed("Tor bootstrap timout (2 minutes) triggered.");
}
} catch (InterruptedException e) {
throw new TorBootstrapFailed(e);
}
}

public void shutdown() throws IOException {
TorControlConnection controlConnection = torControlConnection.orElseThrow();
controlConnection.shutdownTor("SHUTDOWN");
}

@Override
public void onBootstrapStatusEvent(BootstrapEvent bootstrapEvent) {
log.info("Tor bootstrap event: {}", bootstrapEvent);
if (bootstrapEvent.isDoneEvent()) {
isBootstrappedCountdownLatch.countDown();
removeBootstrapEventListener();
}
}

private void addBootstrapEventListener(TorControlConnection controlConnection) throws IOException {
bootstrapEventHandler.addListener(this);
controlConnection.setEventHandler(bootstrapEventHandler);
controlConnection.setEvents(List.of("STATUS_CLIENT"));
}

private void removeBootstrapEventListener() {
TorControlConnection controlConnection = torControlConnection.orElseThrow();
bootstrapEventHandler.removeListener(this);

controlConnection.setEventHandler(null);
try {
controlConnection.setEvents(Collections.emptyList());
} catch (IOException e) {
throw new IllegalStateException("Can't set tor events.");
}
}
}

0 comments on commit f5ebc91

Please sign in to comment.