diff --git a/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java b/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java index bc54d3b944..0ac6ebf04a 100644 --- a/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java +++ b/network/network/src/main/java/bisq/network/p2p/node/transport/TorTransportService.java @@ -7,15 +7,16 @@ import bisq.network.identity.NetworkId; import bisq.network.p2p.node.ConnectionException; import bisq.security.keys.KeyBundle; +import bisq.security.keys.TorKeyPair; import bisq.tor.TorService; import bisq.tor.TorTransportConfig; -import bisq.tor.onionservice.CreateOnionServiceResponse; import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.ServerSocket; import java.net.Socket; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -81,14 +82,19 @@ public ServerSocketResult getServerSocket(NetworkId networkId, KeyBundle keyBund bootstrapInfo.getBootstrapProgress().set(0.25); bootstrapInfo.getBootstrapDetails().set("Create Onion service for node ID '" + networkId + "'"); - CreateOnionServiceResponse response = torService.createOnionService(port, keyBundle.getTorKeyPair()) + TorKeyPair torKeyPair = keyBundle.getTorKeyPair(); + ServerSocket serverSocket = torService.createOnionService(port, torKeyPair) .get(2, TimeUnit.MINUTES); bootstrapInfo.getBootstrapState().set(BootstrapState.SERVICE_PUBLISHED); bootstrapInfo.getBootstrapProgress().set(0.5); - bootstrapInfo.getBootstrapDetails().set("My Onion service address: " + response.getOnionAddress().toString()); - return new ServerSocketResult(response); + String onionAddress = torKeyPair.getOnionAddress(); + bootstrapInfo.getBootstrapDetails().set("My Onion service address: " + onionAddress); + + Address address = new Address(onionAddress); + return new ServerSocketResult(serverSocket, address); + } catch (InterruptedException | ExecutionException | TimeoutException e) { e.printStackTrace(); throw new ConnectionException(e); diff --git a/network/tor/tor/src/main/java/bisq/tor/TorService.java b/network/tor/tor/src/main/java/bisq/tor/TorService.java index 0ddc6895db..5f090326c9 100644 --- a/network/tor/tor/src/main/java/bisq/tor/TorService.java +++ b/network/tor/tor/src/main/java/bisq/tor/TorService.java @@ -23,11 +23,9 @@ import bisq.network.tor.common.torrc.BaseTorrcGenerator; import bisq.network.tor.common.torrc.TorrcFileGenerator; import bisq.security.keys.TorKeyPair; -import bisq.tor.controller.NativeTorController; +import bisq.tor.controller.TorController; import bisq.tor.controller.events.events.BootstrapEvent; import bisq.tor.installer.TorInstaller; -import bisq.tor.onionservice.CreateOnionServiceResponse; -import bisq.tor.onionservice.OnionServicePublishService; import bisq.tor.process.NativeTorProcess; import bisq.tor.process.control_port.ControlPortFilePoller; import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; @@ -40,7 +38,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CompletableFuture; +import java.util.Set; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @Slf4j @@ -49,8 +48,8 @@ public class TorService implements Service { private final TorTransportConfig transportConfig; private final Path torDataDirPath; - private final NativeTorController nativeTorController; - private final OnionServicePublishService onionServicePublishService; + private final TorController torController; + private final Set publishedOnionServices = new CopyOnWriteArraySet<>(); private final AtomicBoolean isRunning = new AtomicBoolean(); @@ -60,9 +59,7 @@ public class TorService implements Service { public TorService(TorTransportConfig transportConfig) { this.transportConfig = transportConfig; this.torDataDirPath = transportConfig.getDataDir(); - nativeTorController = new NativeTorController(transportConfig.getBootstrapTimeout(), - transportConfig.getHsUploadTimeout()); - this.onionServicePublishService = new OnionServicePublishService(nativeTorController); + torController = new TorController(transportConfig.getBootstrapTimeout(), transportConfig.getHsUploadTimeout()); } @Override @@ -89,19 +86,16 @@ public CompletableFuture initialize() { return new ControlPortFilePoller(controlPortFilePath) .parsePort() .thenAccept(controlPort -> { - nativeTorController.connect(controlPort, Optional.of(hashedControlPassword)); - nativeTorController.bindTorToConnection(); + torController.initialize(controlPort, hashedControlPassword); + torController.bootstrapTor(); - nativeTorController.enableTorNetworking(); - nativeTorController.waitUntilBootstrapped(); - - int port = nativeTorController.getSocksPort().orElseThrow(); + int port = torController.getSocksPort(); torSocksProxyFactory = Optional.of(new TorSocksProxyFactory(port)); }) .thenApply(unused -> true); } else { return CompletableFuture.supplyAsync(() -> { - nativeTorController.connect(9051, Optional.empty()); + torController.initialize(9051); torSocksProxyFactory = Optional.of(new TorSocksProxyFactory(9050)); return true; }); @@ -112,38 +106,46 @@ public CompletableFuture initialize() { public CompletableFuture shutdown() { log.info("shutdown"); return CompletableFuture.supplyAsync(() -> { - nativeTorController.shutdown(); + torController.shutdown(); torProcess.ifPresent(NativeTorProcess::waitUntilExited); return true; }); } - public Observable getBootstrapEvent() { - return nativeTorController.getBootstrapEvent(); - } - - public CompletableFuture createOnionService(int port, TorKeyPair torKeyPair) { + public CompletableFuture createOnionService(int port, TorKeyPair torKeyPair) { log.info("Start hidden service with port {}", port); long ts = System.currentTimeMillis(); try { - @SuppressWarnings("resource") ServerSocket localServerSocket = new ServerSocket(RANDOM_PORT); + var localServerSocket = new ServerSocket(RANDOM_PORT); int localPort = localServerSocket.getLocalPort(); - return onionServicePublishService.publish(torKeyPair, port, localPort) - .thenApply(onionAddress -> { - log.info("Tor hidden service Ready. Took {} ms. Onion address={}", - System.currentTimeMillis() - ts, onionAddress); - return new CreateOnionServiceResponse(localServerSocket, onionAddress); - } - ); - - } catch (IOException e) { + + String onionAddress = torKeyPair.getOnionAddress(); + if (!publishedOnionServices.contains(onionAddress)) { + torController.publish(torKeyPair, port, localPort); + publishedOnionServices.add(onionAddress); + } + + log.info("Tor hidden service Ready. Took {} ms. Onion address={}", + System.currentTimeMillis() - ts, onionAddress); + + return CompletableFuture.completedFuture(localServerSocket); + + } catch (IOException | InterruptedException e) { log.error("Can't create onion service", e); return CompletableFuture.failedFuture(e); } } public boolean isOnionServiceOnline(String onionUrl) { - return nativeTorController.isHiddenServiceAvailable(onionUrl); + try { + return torController.isOnionServiceOnline(onionUrl).get(1, TimeUnit.MINUTES); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new RuntimeException(e); + } + } + + public Observable getBootstrapEvent() { + return torController.getBootstrapEvent(); } public Socket getSocket(String streamId) throws IOException { diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java index 5b33d0011c..b4911b9084 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorControlProtocol.java @@ -4,13 +4,17 @@ import bisq.security.keys.TorKeyPair; import bisq.tor.controller.events.listener.BootstrapEventListener; import bisq.tor.controller.events.listener.HsDescEventListener; +import bisq.tor.controller.exceptions.CannotConnectWithTorException; +import bisq.tor.controller.exceptions.CannotSendCommandToTorException; import net.freehaven.tor.control.PasswordDigest; import java.io.IOException; import java.io.OutputStream; +import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -18,28 +22,38 @@ public class TorControlProtocol implements AutoCloseable { private final Socket controlSocket; private final WhonixTorControlReader whonixTorControlReader; - private final OutputStream outputStream; + private Optional outputStream = Optional.empty(); // MidReplyLine = StatusCode "-" ReplyLine // DataReplyLine = StatusCode "+" ReplyLine CmdData private final Pattern multiLineReplyPattern = Pattern.compile("^\\d+[-+].+"); - public TorControlProtocol(int port) throws IOException { - controlSocket = new Socket("127.0.0.1", port); - whonixTorControlReader = new WhonixTorControlReader(controlSocket.getInputStream()); - outputStream = controlSocket.getOutputStream(); + public TorControlProtocol() { + controlSocket = new Socket(); + whonixTorControlReader = new WhonixTorControlReader(); } - @Override - public void close() throws IOException { - controlSocket.close(); + public void initialize(int port) { + try { + var socketAddress = new InetSocketAddress("127.0.0.1", port); + controlSocket.connect(socketAddress); + whonixTorControlReader.start(controlSocket.getInputStream()); + outputStream = Optional.of(controlSocket.getOutputStream()); + } catch (IOException e) { + throw new CannotConnectWithTorException(e); + } } - public void initialize() { - whonixTorControlReader.start(); + @Override + public void close() { + try { + controlSocket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } } - public void authenticate(PasswordDigest passwordDigest) throws IOException { + public void authenticate(PasswordDigest passwordDigest) { byte[] secret = passwordDigest.getSecret(); String secretHex = Hex.encode(secret); String command = "AUTHENTICATE " + secretHex + "\r\n"; @@ -56,7 +70,7 @@ public void authenticate(PasswordDigest passwordDigest) throws IOException { } } - public void addOnion(TorKeyPair torKeyPair, int onionPort, int localPort) throws IOException { + public void addOnion(TorKeyPair torKeyPair, int onionPort, int localPort) { String base64SecretScalar = torKeyPair.getBase64SecretScalar(); String command = "ADD_ONION " + "ED25519-V3:" + base64SecretScalar + " Port=" + onionPort + "," + localPort + "\r\n"; @@ -65,14 +79,14 @@ public void addOnion(TorKeyPair torKeyPair, int onionPort, int localPort) throws assertTwoLineOkReply(replyStream, "ADD_ONION"); } - public String getInfo(String keyword) throws IOException { + public String getInfo(String keyword) { String command = "GETINFO " + keyword + "\r\n"; sendCommand(command); Stream replyStream = receiveReply(); return assertTwoLineOkReply(replyStream, "GETINFO"); } - public void hsFetch(String hsAddress) throws IOException { + public void hsFetch(String hsAddress) { String command = "HSFETCH " + hsAddress + "\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -81,7 +95,7 @@ public void hsFetch(String hsAddress) throws IOException { } } - public void resetConf(String configName) throws IOException { + public void resetConf(String configName) { String command = "RESETCONF " + configName + "\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -90,7 +104,7 @@ public void resetConf(String configName) throws IOException { } } - public void setConfig(String configName, String configValue) throws IOException { + public void setConfig(String configName, String configValue) { String command = "SETCONF " + configName + "=" + configValue + "\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -99,7 +113,7 @@ public void setConfig(String configName, String configValue) throws IOException } } - public void setEvents(List events) throws IOException { + public void setEvents(List events) { var stringBuilder = new StringBuffer("SETEVENTS"); events.forEach(event -> stringBuilder.append(" ").append(event)); stringBuilder.append("\r\n"); @@ -112,7 +126,7 @@ public void setEvents(List events) throws IOException { } } - public void takeOwnership() throws IOException { + public void takeOwnership() { String command = "TAKEOWNERSHIP\r\n"; sendCommand(command); String reply = receiveReply().findFirst().orElseThrow(); @@ -137,10 +151,15 @@ public void removeHsDescEventListener(HsDescEventListener listener) { whonixTorControlReader.removeHsDescEventListener(listener); } - private void sendCommand(String command) throws IOException { - byte[] commandBytes = command.getBytes(StandardCharsets.US_ASCII); - outputStream.write(commandBytes); - outputStream.flush(); + private void sendCommand(String command) { + try { + @SuppressWarnings("resource") OutputStream outputStream = this.outputStream.orElseThrow(); + byte[] commandBytes = command.getBytes(StandardCharsets.US_ASCII); + outputStream.write(commandBytes); + outputStream.flush(); + } catch (IOException e) { + throw new CannotSendCommandToTorException(e); + } } private Stream receiveReply() { diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java index 9c6612fd10..df78fa1a23 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/TorController.java @@ -15,17 +15,21 @@ import lombok.extern.slf4j.Slf4j; import net.freehaven.tor.control.PasswordDigest; -import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; @Slf4j public class TorController implements BootstrapEventListener, HsDescEventListener { + private final TorControlProtocol torControlProtocol = new TorControlProtocol(); + private final int bootstrapTimeout; // in ms private final int hsUploadTimeout; // in ms private final CountDownLatch isBootstrappedCountdownLatch = new CountDownLatch(1); @@ -36,66 +40,52 @@ public class TorController implements BootstrapEventListener, HsDescEventListene private final Map> pendingIsOnionServiceOnlineLookupFutureMap = new ConcurrentHashMap<>(); - private Optional torControlProtocol = Optional.empty(); - public TorController(int bootstrapTimeout, int hsUploadTimeout) { this.bootstrapTimeout = bootstrapTimeout; this.hsUploadTimeout = hsUploadTimeout; } - public void initialize(int controlPort) throws IOException { + public void initialize(int controlPort) { initialize(controlPort, Optional.empty()); } - public void initialize(int controlPort, PasswordDigest hashedControlPassword) throws IOException { + public void initialize(int controlPort, PasswordDigest hashedControlPassword) { initialize(controlPort, Optional.of(hashedControlPassword)); } public void shutdown() { - torControlProtocol.ifPresent(torControlProtocol -> { - try { - torControlProtocol.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + torControlProtocol.close(); } - public void bootstrapTor() throws IOException { + public void bootstrapTor() { bindToBisq(); subscribeToBootstrapEvents(); enableNetworking(); waitUntilBootstrapped(); } - public CompletableFuture isOnionServiceOnline(String onionAddress) throws IOException, ExecutionException, InterruptedException, TimeoutException { + public CompletableFuture isOnionServiceOnline(String onionAddress) { var onionServiceLookupCompletableFuture = new CompletableFuture(); pendingIsOnionServiceOnlineLookupFutureMap.put(onionAddress, onionServiceLookupCompletableFuture); subscribeToHsDescEvents(); - TorControlProtocol torControlProtocol = getTorControlProtocol(); String serviceId = onionAddress.replace(".onion", ""); torControlProtocol.hsFetch(serviceId); onionServiceLookupCompletableFuture.thenRun(() -> { torControlProtocol.removeHsDescEventListener(this); - try { - torControlProtocol.setEvents(Collections.emptyList()); - } catch (IOException e) { - throw new RuntimeException(e); - } + torControlProtocol.setEvents(Collections.emptyList()); }); return onionServiceLookupCompletableFuture; } - public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) throws IOException, InterruptedException { + public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) throws InterruptedException { String onionAddress = torKeyPair.getOnionAddress(); var onionServicePublishedLatch = new CountDownLatch(1); pendingOnionServicePublishLatchMap.put(onionAddress, onionServicePublishedLatch); subscribeToHsDescEvents(); - TorControlProtocol torControlProtocol = getTorControlProtocol(); torControlProtocol.addOnion(torKeyPair, onionServicePort, localPort); boolean isSuccess = onionServicePublishedLatch.await(hsUploadTimeout, TimeUnit.MILLISECONDS); @@ -107,28 +97,20 @@ public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) torControlProtocol.setEvents(Collections.emptyList()); } - public Optional getSocksPort() { - try { - TorControlProtocol torControlProtocol = getTorControlProtocol(); - String socksListenersString = torControlProtocol.getInfo("net/listeners/socks"); - - String socksListener; - if (socksListenersString.contains(" ")) { - String[] socksPorts = socksListenersString.split(" "); - socksListener = socksPorts[0]; - } else { - socksListener = socksListenersString; - } - - // "127.0.0.1:12345" - socksListener = socksListener.replace("\"", ""); - String portString = socksListener.split(":")[1]; - - int port = Integer.parseInt(portString); - return Optional.of(port); - } catch (IOException e) { - return Optional.empty(); + public int getSocksPort() { + String socksListenersString = torControlProtocol.getInfo("net/listeners/socks"); + String socksListener; + if (socksListenersString.contains(" ")) { + String[] socksPorts = socksListenersString.split(" "); + socksListener = socksPorts[0]; + } else { + socksListener = socksListenersString; } + + // "127.0.0.1:12345" + socksListener = socksListener.replace("\"", ""); + String portString = socksListener.split(":")[1]; + return Integer.parseInt(portString); } @Override @@ -174,50 +156,35 @@ public void onHsDescEvent(HsDescEvent hsDescEvent) { } } - private void initialize(int controlPort, Optional hashedControlPassword) throws IOException { - var torControlProtocol = new TorControlProtocol(controlPort); - this.torControlProtocol = Optional.of(torControlProtocol); - - torControlProtocol.initialize(); - if (hashedControlPassword.isPresent()) { - torControlProtocol.authenticate(hashedControlPassword.get()); - } + private void initialize(int controlPort, Optional hashedControlPassword) { + torControlProtocol.initialize(controlPort); + hashedControlPassword.ifPresent(torControlProtocol::authenticate); } - private void bindToBisq() throws IOException { - TorControlProtocol torControlProtocol = getTorControlProtocol(); + private void bindToBisq() { torControlProtocol.takeOwnership(); torControlProtocol.resetConf(NativeTorProcess.ARG_OWNER_PID); } - private void subscribeToBootstrapEvents() throws IOException { - TorControlProtocol torControlProtocol = getTorControlProtocol(); + private void subscribeToBootstrapEvents() { torControlProtocol.addBootstrapEventListener(this); torControlProtocol.setEvents(List.of("STATUS_CLIENT")); } - private void subscribeToHsDescEvents() throws IOException { - TorControlProtocol torControlProtocol = getTorControlProtocol(); + private void subscribeToHsDescEvents() { torControlProtocol.addHsDescEventListener(this); torControlProtocol.setEvents(List.of("HS_DESC")); } - private void enableNetworking() throws IOException { - TorControlProtocol torControlProtocol = getTorControlProtocol(); + private void enableNetworking() { torControlProtocol.setConfig(TorrcClientConfigFactory.DISABLE_NETWORK_CONFIG_KEY, "0"); } private void waitUntilBootstrapped() { try { while (true) { - if (torControlProtocol.isEmpty()) { - throw new TorBootstrapFailedException("Tor is not initializing."); - } - boolean isSuccess = isBootstrappedCountdownLatch.await(bootstrapTimeout, TimeUnit.MILLISECONDS); - if (isSuccess) { - TorControlProtocol torControlProtocol = this.torControlProtocol.get(); torControlProtocol.removeBootstrapEventListener(this); torControlProtocol.setEvents(Collections.emptyList()); break; @@ -227,8 +194,6 @@ private void waitUntilBootstrapped() { } } catch (InterruptedException e) { throw new TorBootstrapFailedException(e); - } catch (IOException e) { - throw new RuntimeException(e); } } @@ -238,8 +203,4 @@ private boolean isBootstrapTimeoutTriggered() { Instant bootstrapTimeoutAgo = Instant.now().minus(bootstrapTimeout, ChronoUnit.MILLIS); return bootstrapTimeoutAgo.isAfter(timestamp); } - - private TorControlProtocol getTorControlProtocol() { - return this.torControlProtocol.orElseThrow(); - } } diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java b/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java index 8899ff6747..b6580dd33b 100644 --- a/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java +++ b/network/tor/tor/src/main/java/bisq/tor/controller/WhonixTorControlReader.java @@ -19,20 +19,17 @@ @Slf4j public class WhonixTorControlReader implements AutoCloseable { - private final BufferedReader bufferedReader; private final BlockingQueue replies = new LinkedBlockingQueue<>(); private final List bootstrapEventListeners = new CopyOnWriteArrayList<>(); private final List hsDescEventListeners = new CopyOnWriteArrayList<>(); private Optional workerThread = Optional.empty(); - public WhonixTorControlReader(InputStream inputStream) { - bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.US_ASCII)); - } - - public void start() { + public void start(InputStream inputStream) { Thread thread = new Thread(() -> { try { + var bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.US_ASCII)); + String line; while ((line = bufferedReader.readLine()) != null) { diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotConnectWithTorException.java b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotConnectWithTorException.java new file mode 100644 index 0000000000..dea197dab9 --- /dev/null +++ b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotConnectWithTorException.java @@ -0,0 +1,7 @@ +package bisq.tor.controller.exceptions; + +public class CannotConnectWithTorException extends RuntimeException { + public CannotConnectWithTorException(Throwable cause) { + super(cause); + } +} diff --git a/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotSendCommandToTorException.java b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotSendCommandToTorException.java new file mode 100644 index 0000000000..42b26f7d92 --- /dev/null +++ b/network/tor/tor/src/main/java/bisq/tor/controller/exceptions/CannotSendCommandToTorException.java @@ -0,0 +1,7 @@ +package bisq.tor.controller.exceptions; + +public class CannotSendCommandToTorException extends RuntimeException { + public CannotSendCommandToTorException(Throwable cause) { + super(cause); + } +} diff --git a/network/tor/tor/src/main/java/bisq/tor/onionservice/OnionServicePublishService.java b/network/tor/tor/src/main/java/bisq/tor/onionservice/OnionServicePublishService.java deleted file mode 100644 index 0d52b0e46a..0000000000 --- a/network/tor/tor/src/main/java/bisq/tor/onionservice/OnionServicePublishService.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 . - */ - -package bisq.tor.onionservice; - -import bisq.security.keys.TorKeyPair; -import bisq.tor.controller.NativeTorController; -import lombok.extern.slf4j.Slf4j; -import net.freehaven.tor.control.TorControlConnection; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -@Slf4j -public class OnionServicePublishService { - private final NativeTorController nativeTorController; - private final Map> onionAddressMap = new HashMap<>(); - - public OnionServicePublishService(NativeTorController nativeTorController) { - this.nativeTorController = nativeTorController; - } - - public synchronized CompletableFuture publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) { - String onionAddressString = torKeyPair.getOnionAddress(); - if (onionAddressMap.containsKey(onionAddressString)) { - return onionAddressMap.get(onionAddressString); - } - - CompletableFuture completableFuture = new CompletableFuture<>(); - onionAddressMap.put(onionAddressString, completableFuture); - - try { - TorControlConnection.CreateHiddenServiceResult jTorResult = - nativeTorController.createHiddenService(onionServicePort, localPort, torKeyPair); - - var onionAddress = new OnionAddress(jTorResult.serviceID + ".onion", onionServicePort); - completableFuture.complete(onionAddress); - - } catch (IOException e) { - log.error("Couldn't create hidden service"); - completableFuture.completeExceptionally(e); - } - - return completableFuture; - } - - public synchronized Optional findOnionAddress(String onionAddressString) { - try { - CompletableFuture completableFuture = onionAddressMap.get(onionAddressString); - if (completableFuture == null) { - return Optional.empty(); - } - - OnionAddress onionAddress = completableFuture.get(); - return Optional.of(onionAddress); - - } catch (ExecutionException | InterruptedException e) { - throw new RuntimeException(e); - } - } -}