From 0ac67239e39300a66bffbd812adbbea3869139a7 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Thu, 25 Feb 2016 13:59:43 +0000 Subject: [PATCH] Fixed race condition when closing redundant sockets. When more than one invitation socket is opened, Alice should pick which one to use and Bob should use whichever one Alice picks. This fixes a race condition where each party picked a different socket and closed the other. --- .../plugins/droidtooth/DroidtoothPlugin.java | 173 ++++++++++------- .../droidtooth/DroidtoothPluginFactory.java | 6 +- .../briarproject/plugins/tor/TorPlugin.java | 2 +- .../api/plugins/duplex/DuplexPlugin.java | 2 +- .../invitation/AliceConnector.java | 2 +- .../briarproject/invitation/BobConnector.java | 2 +- .../briarproject/invitation/Connector.java | 6 +- .../briarproject/plugins/tcp/TcpPlugin.java | 2 +- .../briarproject/util/LatchedReference.java | 30 --- .../plugins/bluetooth/BluetoothPlugin.java | 182 ++++++++++-------- .../bluetooth/BluetoothPluginFactory.java | 8 +- .../plugins/modem/ModemPlugin.java | 2 +- .../plugins/DuplexClientTest.java | 111 ----------- .../plugins/DuplexServerTest.java | 114 ----------- .../org/briarproject/plugins/DuplexTest.java | 102 ---------- 15 files changed, 220 insertions(+), 524 deletions(-) delete mode 100644 briar-core/src/org/briarproject/util/LatchedReference.java delete mode 100644 briar-tests/src/org/briarproject/plugins/DuplexClientTest.java delete mode 100644 briar-tests/src/org/briarproject/plugins/DuplexServerTest.java delete mode 100644 briar-tests/src/org/briarproject/plugins/DuplexTest.java diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java index 0cb68003a1..16d40d43a9 100644 --- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java +++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java @@ -19,11 +19,10 @@ import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.properties.TransportProperties; -import org.briarproject.api.system.Clock; -import org.briarproject.util.LatchedReference; import org.briarproject.util.StringUtils; import java.io.IOException; +import java.io.InputStream; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; @@ -33,9 +32,12 @@ import java.util.Map; import java.util.Map.Entry; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Future; import java.util.logging.Logger; import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED; @@ -69,7 +71,6 @@ class DroidtoothPlugin implements DuplexPlugin { private final AndroidExecutor androidExecutor; private final Context appContext; private final SecureRandom secureRandom; - private final Clock clock; private final Backoff backoff; private final DuplexPluginCallback callback; private final int maxLatency; @@ -83,13 +84,12 @@ class DroidtoothPlugin implements DuplexPlugin { private volatile BluetoothAdapter adapter = null; DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor, - Context appContext, SecureRandom secureRandom, Clock clock, - Backoff backoff, DuplexPluginCallback callback, int maxLatency) { + Context appContext, SecureRandom secureRandom, Backoff backoff, + DuplexPluginCallback callback, int maxLatency) { this.ioExecutor = ioExecutor; this.androidExecutor = androidExecutor; this.appContext = appContext; this.secureRandom = secureRandom; - this.clock = clock; this.backoff = backoff; this.callback = callback; this.maxLatency = maxLatency; @@ -339,7 +339,7 @@ class DroidtoothPlugin implements DuplexPlugin { } public DuplexTransportConnection createInvitationConnection(PseudoRandom r, - long timeout) { + long timeout, boolean alice) { if (!isRunning()) return null; // Use the invitation codes to generate the UUID byte[] b = r.nextBytes(UUID_BYTES); @@ -353,23 +353,67 @@ class DroidtoothPlugin implements DuplexPlugin { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return null; } - // Start the background threads - LatchedReference<BluetoothSocket> socketLatch = - new LatchedReference<BluetoothSocket>(); - new DiscoveryThread(socketLatch, uuid.toString(), timeout).start(); - new BluetoothListenerThread(socketLatch, ss).start(); - // Wait for an incoming or outgoing connection + // Create the background tasks + CompletionService<BluetoothSocket> complete = + new ExecutorCompletionService<BluetoothSocket>(ioExecutor); + List<Future<BluetoothSocket>> futures = + new ArrayList<Future<BluetoothSocket>>(); + if (alice) { + // Return the first connected socket + futures.add(complete.submit(new ListeningTask(ss))); + futures.add(complete.submit(new DiscoveryTask(uuid.toString()))); + } else { + // Return the first socket with readable data + futures.add(complete.submit(new ReadableTask( + new ListeningTask(ss)))); + futures.add(complete.submit(new ReadableTask( + new DiscoveryTask(uuid.toString())))); + } + BluetoothSocket chosen = null; try { - BluetoothSocket s = socketLatch.waitForReference(timeout); - if (s != null) return new DroidtoothTransportConnection(this, s); + Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS); + if (f == null) return null; // No task completed within the timeout + chosen = f.get(); + return new DroidtoothTransportConnection(this, chosen); } catch (InterruptedException e) { - LOG.warning("Interrupted while exchanging invitations"); - Thread.currentThread().interrupt(); + LOG.info("Interrupted while waiting for connection"); + return null; + } catch (ExecutionException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; } finally { - // Closing the socket will terminate the listener thread + // Closing the socket will terminate the listener task tryToClose(ss); + closeSockets(futures, chosen); } - return null; + } + + private void closeSockets(final List<Future<BluetoothSocket>> futures, + final BluetoothSocket chosen) { + ioExecutor.execute(new Runnable() { + public void run() { + for (Future<BluetoothSocket> f : futures) { + try { + if (f.cancel(true)) { + LOG.info("Cancelled task"); + } else { + BluetoothSocket s = f.get(); + if (s != null && s != chosen) { + LOG.info("Closing unwanted socket"); + s.close(); + } + } + } catch (InterruptedException e) { + LOG.info("Interrupted while closing sockets"); + return; + } catch (ExecutionException e) { + if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + } catch (IOException e) { + if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + } + } + } + }); } private class BluetoothStateReceiver extends BroadcastReceiver { @@ -395,61 +439,37 @@ class DroidtoothPlugin implements DuplexPlugin { } } - private class DiscoveryThread extends Thread { + private class DiscoveryTask implements Callable<BluetoothSocket> { - private final LatchedReference<BluetoothSocket> socketLatch; private final String uuid; - private final long timeout; - private DiscoveryThread(LatchedReference<BluetoothSocket> socketLatch, - String uuid, long timeout) { - this.socketLatch = socketLatch; + private DiscoveryTask(String uuid) { this.uuid = uuid; - this.timeout = timeout; } @Override - public void run() { - long end = clock.currentTimeMillis() + timeout; - while (!finished(end)) { + public BluetoothSocket call() throws Exception { + // Repeat discovery until we connect or get interrupted + while (true) { // Discover nearby devices LOG.info("Discovering nearby devices"); - List<String> addresses; - try { - long now = clock.currentTimeMillis(); - addresses = discoverDevices(end - now); - } catch (InterruptedException e) { - LOG.warning("Interrupted while discovering devices"); - Thread.currentThread().interrupt(); - return; - } + List<String> addresses = discoverDevices(); if (addresses.isEmpty()) { LOG.info("No devices discovered"); continue; } // Connect to any device with the right UUID for (String address : addresses) { - if (finished(end)) return; BluetoothSocket s = connect(address, uuid); if (s != null) { LOG.info("Outgoing connection"); - if (!socketLatch.set(s)) { - LOG.info("Closing redundant connection"); - tryToClose(s); - } - return; + return s; } } } } - private boolean finished(long end) { - long now = clock.currentTimeMillis(); - return now >= end || !isRunning() || socketLatch.isSet(); - } - - private List<String> discoverDevices(long timeout) - throws InterruptedException { + private List<String> discoverDevices() throws InterruptedException { IntentFilter filter = new IntentFilter(); filter.addAction(FOUND); filter.addAction(DISCOVERY_FINISHED); @@ -457,7 +477,7 @@ class DroidtoothPlugin implements DuplexPlugin { appContext.registerReceiver(disco, filter); LOG.info("Starting discovery"); adapter.startDiscovery(); - return disco.waitForAddresses(timeout); + return disco.waitForAddresses(); } } @@ -481,38 +501,47 @@ class DroidtoothPlugin implements DuplexPlugin { } } - private List<String> waitForAddresses(long timeout) - throws InterruptedException { - finished.await(timeout, MILLISECONDS); + private List<String> waitForAddresses() throws InterruptedException { + finished.await(); + Collections.shuffle(addresses); return Collections.unmodifiableList(addresses); } } - private static class BluetoothListenerThread extends Thread { + private static class ListeningTask implements Callable<BluetoothSocket> { - private final LatchedReference<BluetoothSocket> socketLatch; private final BluetoothServerSocket serverSocket; - private BluetoothListenerThread( - LatchedReference<BluetoothSocket> socketLatch, - BluetoothServerSocket serverSocket) { - this.socketLatch = socketLatch; + private ListeningTask(BluetoothServerSocket serverSocket) { this.serverSocket = serverSocket; } @Override - public void run() { - try { - BluetoothSocket s = serverSocket.accept(); - LOG.info("Incoming connection"); - if (!socketLatch.set(s)) { - LOG.info("Closing redundant connection"); - s.close(); - } - } catch (IOException e) { - // This is expected when the socket is closed - if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + public BluetoothSocket call() throws IOException { + BluetoothSocket s = serverSocket.accept(); + LOG.info("Incoming connection"); + return s; + } + } + + private static class ReadableTask implements Callable<BluetoothSocket> { + + private final Callable<BluetoothSocket> connectionTask; + + private ReadableTask(Callable<BluetoothSocket> connectionTask) { + this.connectionTask = connectionTask; + } + + @Override + public BluetoothSocket call() throws Exception { + BluetoothSocket s = connectionTask.call(); + InputStream in = s.getInputStream(); + while (in.available() == 0) { + LOG.info("Waiting for data"); + Thread.sleep(1000); } + LOG.info("Data available"); + return s; } } } diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java index 15e2fc0bac..db89929a22 100644 --- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java +++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java @@ -9,8 +9,6 @@ import org.briarproject.api.plugins.BackoffFactory; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexPluginFactory; -import org.briarproject.api.system.Clock; -import org.briarproject.system.SystemClock; import java.security.SecureRandom; import java.util.concurrent.Executor; @@ -27,7 +25,6 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory { private final Context appContext; private final SecureRandom secureRandom; private final BackoffFactory backoffFactory; - private final Clock clock; public DroidtoothPluginFactory(Executor ioExecutor, AndroidExecutor androidExecutor, Context appContext, @@ -37,7 +34,6 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory { this.appContext = appContext; this.secureRandom = secureRandom; this.backoffFactory = backoffFactory; - clock = new SystemClock(); } public TransportId getId() { @@ -48,6 +44,6 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext, - secureRandom, clock, backoff, callback, MAX_LATENCY); + secureRandom, backoff, callback, MAX_LATENCY); } } diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index d98c213436..01d10f91a8 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -565,7 +565,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, } public DuplexTransportConnection createInvitationConnection(PseudoRandom r, - long timeout) { + long timeout, boolean alice) { throw new UnsupportedOperationException(); } diff --git a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java index b64f4a1498..519400b563 100644 --- a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java +++ b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java @@ -23,5 +23,5 @@ public interface DuplexPlugin extends Plugin { * time. */ DuplexTransportConnection createInvitationConnection(PseudoRandom r, - long timeout); + long timeout, boolean alice); } diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java index 39945bf6b6..210d173d9e 100644 --- a/briar-core/src/org/briarproject/invitation/AliceConnector.java +++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java @@ -51,7 +51,7 @@ class AliceConnector extends Connector { @Override public void run() { // Create an incoming or outgoing connection - DuplexTransportConnection conn = createInvitationConnection(); + DuplexTransportConnection conn = createInvitationConnection(true); if (conn == null) return; if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected"); // Don't proceed with more than one connection diff --git a/briar-core/src/org/briarproject/invitation/BobConnector.java b/briar-core/src/org/briarproject/invitation/BobConnector.java index 53bdbe3e47..f2c6e26196 100644 --- a/briar-core/src/org/briarproject/invitation/BobConnector.java +++ b/briar-core/src/org/briarproject/invitation/BobConnector.java @@ -51,7 +51,7 @@ class BobConnector extends Connector { @Override public void run() { // Create an incoming or outgoing connection - DuplexTransportConnection conn = createInvitationConnection(); + DuplexTransportConnection conn = createInvitationConnection(false); if (conn == null) return; if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected"); // Carry out the key agreement protocol diff --git a/briar-core/src/org/briarproject/invitation/Connector.java b/briar-core/src/org/briarproject/invitation/Connector.java index 6310c4f170..2934a0716e 100644 --- a/briar-core/src/org/briarproject/invitation/Connector.java +++ b/briar-core/src/org/briarproject/invitation/Connector.java @@ -93,10 +93,12 @@ abstract class Connector extends Thread { messageDigest = crypto.getMessageDigest(); } - protected DuplexTransportConnection createInvitationConnection() { + protected DuplexTransportConnection createInvitationConnection( + boolean alice) { if (LOG.isLoggable(INFO)) LOG.info(pluginName + " creating invitation connection"); - return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT); + return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT, + alice); } protected void sendPublicKeyHash(BdfWriter w) throws IOException { diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java index 64f91c4d1a..54376310a0 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java @@ -246,7 +246,7 @@ abstract class TcpPlugin implements DuplexPlugin { } public DuplexTransportConnection createInvitationConnection(PseudoRandom r, - long timeout) { + long timeout, boolean alice) { throw new UnsupportedOperationException(); } diff --git a/briar-core/src/org/briarproject/util/LatchedReference.java b/briar-core/src/org/briarproject/util/LatchedReference.java deleted file mode 100644 index 43763bfa79..0000000000 --- a/briar-core/src/org/briarproject/util/LatchedReference.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.briarproject.util; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -public class LatchedReference<T> { - - private final CountDownLatch latch = new CountDownLatch(1); - private final AtomicReference<T> reference = new AtomicReference<T>(); - - public boolean isSet() { - return reference.get() != null; - } - - public boolean set(T t) { - if (t == null) throw new IllegalArgumentException(); - if (reference.compareAndSet(null, t)) { - latch.countDown(); - return true; - } - return false; - } - - public T waitForReference(long timeout) throws InterruptedException { - latch.await(timeout, MILLISECONDS); - return reference.get(); - } -} diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java index 786dcf2228..669965f0d3 100644 --- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java +++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java @@ -8,18 +8,24 @@ import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.properties.TransportProperties; -import org.briarproject.api.system.Clock; -import org.briarproject.util.LatchedReference; import org.briarproject.util.OsUtils; import org.briarproject.util.StringUtils; import java.io.IOException; +import java.io.InputStream; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.logging.Logger; @@ -30,6 +36,7 @@ import javax.microedition.io.Connector; import javax.microedition.io.StreamConnection; import javax.microedition.io.StreamConnectionNotifier; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static javax.bluetooth.DiscoveryAgent.GIAC; @@ -45,7 +52,6 @@ class BluetoothPlugin implements DuplexPlugin { private final Executor ioExecutor; private final SecureRandom secureRandom; - private final Clock clock; private final Backoff backoff; private final DuplexPluginCallback callback; private final int maxLatency; @@ -55,11 +61,10 @@ class BluetoothPlugin implements DuplexPlugin { private volatile StreamConnectionNotifier socket = null; private volatile LocalDevice localDevice = null; - BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, Clock clock, + BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, Backoff backoff, DuplexPluginCallback callback, int maxLatency) { this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; - this.clock = clock; this.backoff = backoff; this.callback = callback; this.maxLatency = maxLatency; @@ -246,7 +251,7 @@ class BluetoothPlugin implements DuplexPlugin { } public DuplexTransportConnection createInvitationConnection(PseudoRandom r, - long timeout) { + long timeout, boolean alice) { if (!running) return null; // Use the invitation codes to generate the UUID byte[] b = r.nextBytes(UUID_BYTES); @@ -266,23 +271,67 @@ class BluetoothPlugin implements DuplexPlugin { tryToClose(ss); return null; } - // Start the background threads - LatchedReference<StreamConnection> socketLatch = - new LatchedReference<StreamConnection>(); - new DiscoveryThread(socketLatch, uuid, timeout).start(); - new BluetoothListenerThread(socketLatch, ss).start(); - // Wait for an incoming or outgoing connection + // Create the background tasks + CompletionService<StreamConnection> complete = + new ExecutorCompletionService<StreamConnection>(ioExecutor); + List<Future<StreamConnection>> futures = + new ArrayList<Future<StreamConnection>>(); + if (alice) { + // Return the first connected socket + futures.add(complete.submit(new ListeningTask(ss))); + futures.add(complete.submit(new DiscoveryTask(uuid))); + } else { + // Return the first socket with readable data + futures.add(complete.submit(new ReadableTask( + new ListeningTask(ss)))); + futures.add(complete.submit(new ReadableTask( + new DiscoveryTask(uuid)))); + } + StreamConnection chosen = null; try { - StreamConnection s = socketLatch.waitForReference(timeout); - if (s != null) return new BluetoothTransportConnection(this, s); + Future<StreamConnection> f = complete.poll(timeout, MILLISECONDS); + if (f == null) return null; // No task completed within the timeout + chosen = f.get(); + return new BluetoothTransportConnection(this, chosen); } catch (InterruptedException e) { - LOG.warning("Interrupted while exchanging invitations"); - Thread.currentThread().interrupt(); + LOG.info("Interrupted while waiting for connection"); + return null; + } catch (ExecutionException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; } finally { - // Closing the socket will terminate the listener thread + // Closing the socket will terminate the listener task tryToClose(ss); + closeSockets(futures, chosen); } - return null; + } + + private void closeSockets(final List<Future<StreamConnection>> futures, + final StreamConnection chosen) { + ioExecutor.execute(new Runnable() { + public void run() { + for (Future<StreamConnection> f : futures) { + try { + if (f.cancel(true)) { + LOG.info("Cancelled task"); + } else { + StreamConnection s = f.get(); + if (s != null && s != chosen) { + LOG.info("Closing unwanted socket"); + s.close(); + } + } + } catch (InterruptedException e) { + LOG.info("Interrupted while closing sockets"); + return; + } catch (ExecutionException e) { + if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + } catch (IOException e) { + if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + } + } + } + }); } private void makeDeviceDiscoverable() { @@ -294,93 +343,74 @@ class BluetoothPlugin implements DuplexPlugin { } } - private class DiscoveryThread extends Thread { + private class DiscoveryTask implements Callable<StreamConnection> { - private final LatchedReference<StreamConnection> socketLatch; private final String uuid; - private final long timeout; - private DiscoveryThread(LatchedReference<StreamConnection> socketLatch, - String uuid, long timeout) { - this.socketLatch = socketLatch; + private DiscoveryTask(String uuid) { this.uuid = uuid; - this.timeout = timeout; } @Override - public void run() { + public StreamConnection call() throws Exception { + // Repeat discovery until we connect or get interrupted DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent(); - long now = clock.currentTimeMillis(); - long end = now + timeout; - while (now < end && running && !socketLatch.isSet()) { - if (!discoverySemaphore.tryAcquire()) { - LOG.info("Another device discovery is in progress"); - return; - } + while (true) { + if (!discoverySemaphore.tryAcquire()) + throw new Exception("Discovery is already in progress"); try { InvitationListener listener = new InvitationListener(discoveryAgent, uuid); discoveryAgent.startInquiry(GIAC, listener); String url = listener.waitForUrl(); - if (url == null) continue; - StreamConnection s = connect(url); - if (s == null) continue; - LOG.info("Outgoing connection"); - if (!socketLatch.set(s)) { - LOG.info("Closing redundant connection"); - tryToClose(s); + if (url != null) { + StreamConnection s = connect(url); + if (s != null) { + LOG.info("Outgoing connection"); + return s; + } } - return; - } catch (BluetoothStateException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - return; - } catch (InterruptedException e) { - LOG.warning("Interrupted while waiting for URL"); - Thread.currentThread().interrupt(); - return; } finally { discoverySemaphore.release(); } } } - - private void tryToClose(StreamConnection s) { - try { - if (s != null) s.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } } - private static class BluetoothListenerThread extends Thread { + private static class ListeningTask implements Callable<StreamConnection> { - private final LatchedReference<StreamConnection> socketLatch; private final StreamConnectionNotifier serverSocket; - private BluetoothListenerThread( - LatchedReference<StreamConnection> socketLatch, - StreamConnectionNotifier serverSocket) { - this.socketLatch = socketLatch; + private ListeningTask(StreamConnectionNotifier serverSocket) { this.serverSocket = serverSocket; } @Override - public void run() { - LOG.info("Listening for invitation connections"); - // Listen until a connection is received or the socket is closed - try { - StreamConnection s = serverSocket.acceptAndOpen(); - LOG.info("Incoming connection"); - if (!socketLatch.set(s)) { - LOG.info("Closing redundant connection"); - s.close(); - } - } catch (IOException e) { - // This is expected when the socket is closed - if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + public StreamConnection call() throws Exception { + StreamConnection s = serverSocket.acceptAndOpen(); + LOG.info("Incoming connection"); + return s; + } + } + + private static class ReadableTask implements Callable<StreamConnection> { + + private final Callable<StreamConnection> connectionTask; + + private ReadableTask(Callable<StreamConnection> connectionTask) { + this.connectionTask = connectionTask; + } + + @Override + public StreamConnection call() throws Exception { + StreamConnection s = connectionTask.call(); + InputStream in = s.openInputStream(); + while (in.available() == 0) { + LOG.info("Waiting for data"); + Thread.sleep(1000); } + LOG.info("Data available"); + return s; } } } diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java index 5259ece49b..2d7f156e22 100644 --- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java +++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java @@ -6,8 +6,6 @@ import org.briarproject.api.plugins.BackoffFactory; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexPluginFactory; -import org.briarproject.api.system.Clock; -import org.briarproject.system.SystemClock; import java.security.SecureRandom; import java.util.concurrent.Executor; @@ -22,14 +20,12 @@ public class BluetoothPluginFactory implements DuplexPluginFactory { private final Executor ioExecutor; private final SecureRandom secureRandom; private final BackoffFactory backoffFactory; - private final Clock clock; public BluetoothPluginFactory(Executor ioExecutor, SecureRandom secureRandom, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; this.backoffFactory = backoffFactory; - clock = new SystemClock(); } public TransportId getId() { @@ -39,7 +35,7 @@ public class BluetoothPluginFactory implements DuplexPluginFactory { public DuplexPlugin createPlugin(DuplexPluginCallback callback) { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - return new BluetoothPlugin(ioExecutor, secureRandom, clock, backoff, - callback, MAX_LATENCY); + return new BluetoothPlugin(ioExecutor, secureRandom, backoff, callback, + MAX_LATENCY); } } diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java index 0245ad17d3..954529a5e8 100644 --- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java +++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java @@ -154,7 +154,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { } public DuplexTransportConnection createInvitationConnection(PseudoRandom r, - long timeout) { + long timeout, boolean alice) { throw new UnsupportedOperationException(); } diff --git a/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java b/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java deleted file mode 100644 index b241ec4e8f..0000000000 --- a/briar-tests/src/org/briarproject/plugins/DuplexClientTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.briarproject.plugins; - -import org.briarproject.api.contact.ContactId; -import org.briarproject.api.crypto.PseudoRandom; -import org.briarproject.api.plugins.duplex.DuplexPluginCallback; -import org.briarproject.api.plugins.duplex.DuplexTransportConnection; -import org.briarproject.api.properties.TransportProperties; -import org.briarproject.api.settings.Settings; - -import java.io.IOException; -import java.util.Map; - -import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT; - -public abstract class DuplexClientTest extends DuplexTest { - - protected ClientCallback callback = null; - - protected void run() throws IOException { - assert plugin != null; - // Start the plugin - System.out.println("Starting plugin"); - if (!plugin.start()) { - System.out.println("Plugin failed to start"); - return; - } - try { - // Try to connect to the server - System.out.println("Creating connection"); - DuplexTransportConnection d = plugin.createConnection(contactId); - if (d == null) { - System.out.println("Connection failed"); - return; - } else { - System.out.println("Connection created"); - receiveChallengeSendResponse(d); - } - if (!plugin.supportsInvitations()) { - System.out.println("Skipping invitation test"); - return; - } - // Try to create an invitation connection - System.out.println("Creating invitation connection"); - PseudoRandom r = getPseudoRandom(123); - d = plugin.createInvitationConnection(r, CONNECTION_TIMEOUT); - if (d == null) { - System.out.println("Connection failed"); - } else { - System.out.println("Connection created"); - sendChallengeReceiveResponse(d); - } - } finally { - // Stop the plugin - System.out.println("Stopping plugin"); - plugin.stop(); - } - } - - protected static class ClientCallback implements DuplexPluginCallback { - - private Settings settings = null; - private TransportProperties local = null; - private Map<ContactId, TransportProperties> remote = null; - - public ClientCallback(Settings settings, TransportProperties local, - Map<ContactId, TransportProperties> remote) { - this.settings = settings; - this.local = local; - this.remote = remote; - } - - public Settings getSettings() { - return settings; - } - - public TransportProperties getLocalProperties() { - return local; - } - - public Map<ContactId, TransportProperties> getRemoteProperties() { - return remote; - } - - public void mergeSettings(Settings s) { - settings = s; - } - - public void mergeLocalProperties(TransportProperties p) { - local = p; - } - - public int showChoice(String[] options, String... message) { - return -1; - } - - public boolean showConfirmationMessage(String... message) { - return false; - } - - public void showMessage(String... message) {} - - public void incomingConnectionCreated(DuplexTransportConnection d) {} - - public void outgoingConnectionCreated(ContactId contactId, - DuplexTransportConnection d) {} - - public void transportEnabled() {} - - public void transportDisabled() {} - } -} diff --git a/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java b/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java deleted file mode 100644 index ab60c92725..0000000000 --- a/briar-tests/src/org/briarproject/plugins/DuplexServerTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.briarproject.plugins; - -import org.briarproject.api.contact.ContactId; -import org.briarproject.api.plugins.duplex.DuplexPluginCallback; -import org.briarproject.api.plugins.duplex.DuplexTransportConnection; -import org.briarproject.api.properties.TransportProperties; -import org.briarproject.api.settings.Settings; - -import java.util.Map; -import java.util.concurrent.CountDownLatch; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT; - -public abstract class DuplexServerTest extends DuplexTest { - - protected ServerCallback callback = null; - - protected void run() throws Exception { - assert callback != null; - assert plugin != null; - // Start the plugin - System.out.println("Starting plugin"); - if (!plugin.start()) { - System.out.println("Plugin failed to start"); - return; - } - try { - // Wait for a connection - System.out.println("Waiting for connection"); - if (!callback.latch.await(120, SECONDS)) { - System.out.println("No connection received"); - return; - } - if (!plugin.supportsInvitations()) { - System.out.println("Skipping invitation test"); - return; - } - // Try to create an invitation connection - System.out.println("Creating invitation connection"); - DuplexTransportConnection d = plugin.createInvitationConnection( - getPseudoRandom(123), CONNECTION_TIMEOUT); - if (d == null) { - System.out.println("Connection failed"); - } else { - System.out.println("Connection created"); - receiveChallengeSendResponse(d); - } - } finally { - // Stop the plugin - System.out.println("Stopping plugin"); - plugin.stop(); - } - } - - protected class ServerCallback implements DuplexPluginCallback { - - private final CountDownLatch latch = new CountDownLatch(1); - - private Settings settings; - private TransportProperties local; - private Map<ContactId, TransportProperties> remote; - - public ServerCallback(Settings settings, TransportProperties local, - Map<ContactId, TransportProperties> remote) { - this.settings = settings; - this.local = local; - this.remote = remote; - } - - public Settings getSettings() { - return settings; - } - - public TransportProperties getLocalProperties() { - return local; - } - - public Map<ContactId, TransportProperties> getRemoteProperties() { - return remote; - } - - public void mergeSettings(Settings s) { - settings = s; - } - - public void mergeLocalProperties(TransportProperties p) { - local = p; - } - - public int showChoice(String[] options, String... message) { - return -1; - } - - public boolean showConfirmationMessage(String... message) { - return false; - } - - public void showMessage(String... message) {} - - public void incomingConnectionCreated(DuplexTransportConnection d) { - System.out.println("Connection received"); - sendChallengeReceiveResponse(d); - latch.countDown(); - } - - public void outgoingConnectionCreated(ContactId c, - DuplexTransportConnection d) {} - - public void transportEnabled() {} - - public void transportDisabled() {} - } -} diff --git a/briar-tests/src/org/briarproject/plugins/DuplexTest.java b/briar-tests/src/org/briarproject/plugins/DuplexTest.java deleted file mode 100644 index d20418a3f5..0000000000 --- a/briar-tests/src/org/briarproject/plugins/DuplexTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.briarproject.plugins; - -import org.briarproject.api.contact.ContactId; -import org.briarproject.api.crypto.PseudoRandom; -import org.briarproject.api.plugins.TransportConnectionReader; -import org.briarproject.api.plugins.TransportConnectionWriter; -import org.briarproject.api.plugins.duplex.DuplexPlugin; -import org.briarproject.api.plugins.duplex.DuplexTransportConnection; - -import java.io.IOException; -import java.io.PrintStream; -import java.util.Random; -import java.util.Scanner; - -abstract class DuplexTest { - - protected static final String CHALLENGE = "Carrots!"; - protected static final String RESPONSE = "Potatoes!"; - - protected final ContactId contactId = new ContactId(234); - - protected DuplexPlugin plugin = null; - - protected void sendChallengeReceiveResponse(DuplexTransportConnection d) { - assert plugin != null; - TransportConnectionReader r = d.getReader(); - TransportConnectionWriter w = d.getWriter(); - try { - PrintStream out = new PrintStream(w.getOutputStream()); - out.println(CHALLENGE); - out.flush(); - System.out.println("Sent challenge: " + CHALLENGE); - Scanner in = new Scanner(r.getInputStream()); - if (in.hasNextLine()) { - String response = in.nextLine(); - System.out.println("Received response: " + response); - if (RESPONSE.equals(response)) { - System.out.println("Correct response"); - } else { - System.out.println("Incorrect response"); - } - } else { - System.out.println("No response"); - } - r.dispose(false, true); - w.dispose(false); - } catch (IOException e) { - e.printStackTrace(); - try { - r.dispose(true, true); - w.dispose(true); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } - - protected void receiveChallengeSendResponse(DuplexTransportConnection d) { - assert plugin != null; - TransportConnectionReader r = d.getReader(); - TransportConnectionWriter w = d.getWriter(); - try { - Scanner in = new Scanner(r.getInputStream()); - if (in.hasNextLine()) { - String challenge = in.nextLine(); - System.out.println("Received challenge: " + challenge); - if (CHALLENGE.equals(challenge)) { - - PrintStream out = new PrintStream(w.getOutputStream()); - out.println(RESPONSE); - out.flush(); - System.out.println("Sent response: " + RESPONSE); - } else { - System.out.println("Incorrect challenge"); - } - } else { - System.out.println("No challenge"); - } - r.dispose(false, true); - w.dispose(false); - } catch (IOException e) { - e.printStackTrace(); - try { - r.dispose(true, true); - w.dispose(true); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - } - - protected PseudoRandom getPseudoRandom(int seed) { - final Random random = new Random(seed); - return new PseudoRandom() { - public byte[] nextBytes(int bytes) { - byte[] b = new byte[bytes]; - random.nextBytes(b); - return b; - } - }; - } -} -- GitLab