diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java index 2c318a649..188cb0e8a 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java @@ -55,10 +55,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { // Non-null if the plugin started successfully private volatile BluetoothAdapter adapter = null; - AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor, + AndroidBluetoothPlugin(BluetoothConnectionManager connectionManager, + Executor ioExecutor, AndroidExecutor androidExecutor, Context appContext, SecureRandom secureRandom, Backoff backoff, DuplexPluginCallback callback, int maxLatency) { - super(ioExecutor, secureRandom, backoff, callback, maxLatency); + super(connectionManager, ioExecutor, secureRandom, backoff, callback, + maxLatency); this.androidExecutor = androidExecutor; this.appContext = appContext; } @@ -154,7 +156,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { } private DuplexTransportConnection wrapSocket(BluetoothSocket s) { - return new AndroidBluetoothTransportConnection(this, s); + return new AndroidBluetoothTransportConnection(this, + connectionManager, s); } @Override diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java index 57ae5caa8..3c0707cec 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java @@ -59,11 +59,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { @Override public DuplexPlugin createPlugin(DuplexPluginCallback callback) { + BluetoothConnectionManager connectionManager = + new BluetoothConnectionManagerImpl(); Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor, - androidExecutor, appContext, secureRandom, backoff, callback, - MAX_LATENCY); + AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( + connectionManager, ioExecutor, androidExecutor, appContext, + secureRandom, backoff, callback, MAX_LATENCY); eventBus.addListener(plugin); return plugin; } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java index e9b615e0b..15373f213 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java @@ -14,10 +14,14 @@ import java.io.OutputStream; class AndroidBluetoothTransportConnection extends AbstractDuplexTransportConnection { + private final BluetoothConnectionManager connectionManager; private final BluetoothSocket socket; - AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) { + AndroidBluetoothTransportConnection(Plugin plugin, + BluetoothConnectionManager connectionManager, + BluetoothSocket socket) { super(plugin); + this.connectionManager = connectionManager; this.socket = socket; } @@ -33,6 +37,10 @@ class AndroidBluetoothTransportConnection @Override protected void closeConnection(boolean exception) throws IOException { - socket.close(); + try { + socket.close(); + } finally { + connectionManager.connectionClosed(); + } } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothConnectionManager.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothConnectionManager.java new file mode 100644 index 000000000..c6e722396 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothConnectionManager.java @@ -0,0 +1,26 @@ +package org.briarproject.bramble.plugin.bluetooth; + +interface BluetoothConnectionManager { + + /** + * Returns true if a contact connection can be opened without exceeding + * the connection limit. + */ + boolean canOpenConnection(); + + /** + * Increments the number of open connections and returns true if the new + * connection can be kept open without exceeding the connection limit. + */ + boolean connectionOpened(); + + /** + * Decrements the number of open connections. + */ + void connectionClosed(); + + /** + * Resets the number of open connections. + */ + void allConnectionsClosed(); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothConnectionManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothConnectionManagerImpl.java new file mode 100644 index 000000000..7d40e7831 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothConnectionManagerImpl.java @@ -0,0 +1,45 @@ +package org.briarproject.bramble.plugin.bluetooth; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; + +class BluetoothConnectionManagerImpl implements BluetoothConnectionManager { + + private static final int MAX_OPEN_CONNECTIONS = 5; + + private static final Logger LOG = + Logger.getLogger(BluetoothConnectionManagerImpl.class.getName()); + + private final AtomicInteger openConnections = new AtomicInteger(0); + + @Override + public boolean canOpenConnection() { + int open = openConnections.get(); + if (LOG.isLoggable(INFO)) + LOG.info(open + " open connections"); + return open < MAX_OPEN_CONNECTIONS; + } + + @Override + public boolean connectionOpened() { + int open = openConnections.incrementAndGet(); + if (LOG.isLoggable(INFO)) + LOG.info("Connection opened, " + open + " open"); + return open <= MAX_OPEN_CONNECTIONS; + } + + @Override + public void connectionClosed() { + int open = openConnections.decrementAndGet(); + if (LOG.isLoggable(INFO)) + LOG.info("Connection closed, " + open + " open"); + } + + @Override + public void allConnectionsClosed() { + LOG.info("All connections closed"); + openConnections.set(0); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java index ce43261a4..68c64efc3 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java @@ -52,6 +52,8 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { private static final Logger LOG = Logger.getLogger(BluetoothPlugin.class.getName()); + protected final BluetoothConnectionManager connectionManager; + private final Executor ioExecutor; private final SecureRandom secureRandom; private final Backoff backoff; @@ -92,8 +94,10 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { abstract DuplexTransportConnection connectTo(String address, String uuid) throws IOException; - BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, + BluetoothPlugin(BluetoothConnectionManager connectionManager, + Executor ioExecutor, SecureRandom secureRandom, Backoff backoff, DuplexPluginCallback callback, int maxLatency) { + this.connectionManager = connectionManager; this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; this.backoff = backoff; @@ -111,6 +115,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { void onAdapterDisabled() { LOG.info("Bluetooth disabled"); tryToClose(socket); + connectionManager.allConnectionsClosed(); callback.transportDisabled(); } @@ -214,11 +219,25 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { return; } backoff.reset(); - callback.incomingConnectionCreated(conn); + if (connectionManager.connectionOpened()) { + callback.incomingConnectionCreated(conn); + } else { + LOG.info("Closing incoming connection"); + tryToCloseUnusedConnection(conn); + } if (!running) return; } } + private void tryToCloseUnusedConnection(DuplexTransportConnection conn) { + try { + conn.getWriter().dispose(false); + conn.getReader().dispose(false, false); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + @Override public void stop() { running = false; @@ -258,10 +277,19 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { if (StringUtils.isNullOrEmpty(uuid)) continue; ioExecutor.execute(() -> { if (!isRunning() || !shouldAllowContactConnections()) return; + if (!connectionManager.canOpenConnection()) { + LOG.info("Not connecting, too many open connections"); + return; + } DuplexTransportConnection conn = connect(address, uuid); if (conn != null) { backoff.reset(); - callback.outgoingConnectionCreated(c, conn); + if (connectionManager.connectionOpened()) { + callback.outgoingConnectionCreated(c, conn); + } else { + LOG.info("Closing outgoing connection"); + tryToCloseUnusedConnection(conn); + } } }); } @@ -301,12 +329,25 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public DuplexTransportConnection createConnection(ContactId c) { if (!isRunning() || !shouldAllowContactConnections()) return null; + if (!connectionManager.canOpenConnection()) { + LOG.info("Not connecting, too many open connections"); + return null; + } TransportProperties p = callback.getRemoteProperties(c); String address = p.get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) return null; String uuid = p.get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) return null; - return connect(address, uuid); + DuplexTransportConnection conn = connect(address, uuid); + if (conn == null) return null; + // TODO: Why don't we reset the backoff here? + if (connectionManager.connectionOpened()) { + return conn; + } else { + LOG.info("Closing outgoing connection"); + tryToCloseUnusedConnection(conn); + return null; + } } @Override @@ -356,7 +397,10 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { String uuid = UUID.nameUUIDFromBytes(commitment).toString(); if (LOG.isLoggable(INFO)) LOG.info("Connecting to key agreement UUID " + uuid); - return connect(address, uuid); + DuplexTransportConnection conn = connect(address, uuid); + // The connection limit doesn't apply to key agreement + if (conn != null) connectionManager.connectionOpened(); + return conn; } private String parseAddress(BdfList descriptor) throws FormatException { @@ -411,6 +455,8 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { DuplexTransportConnection conn = acceptConnection(ss); if (LOG.isLoggable(INFO)) LOG.info(ID.getString() + ": Incoming connection"); + // The connection limit doesn't apply to key agreement + connectionManager.connectionOpened(); return new KeyAgreementConnection(conn, ID); }; } diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java index 487603e2d..618e8b96e 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java @@ -31,9 +31,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin { // Non-null if the plugin started successfully private volatile LocalDevice localDevice = null; - JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, + JavaBluetoothPlugin(BluetoothConnectionManager connectionManager, + Executor ioExecutor, SecureRandom secureRandom, Backoff backoff, DuplexPluginCallback callback, int maxLatency) { - super(ioExecutor, secureRandom, backoff, callback, maxLatency); + super(connectionManager, ioExecutor, secureRandom, backoff, callback, + maxLatency); } @Override @@ -110,6 +112,6 @@ class JavaBluetoothPlugin extends BluetoothPlugin { } private DuplexTransportConnection wrapSocket(StreamConnection s) { - return new JavaBluetoothTransportConnection(this, s); + return new JavaBluetoothTransportConnection(this, connectionManager, s); } } diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java index 56e7234f6..d3a085d37 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java @@ -51,10 +51,12 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory { @Override public DuplexPlugin createPlugin(DuplexPluginCallback callback) { + BluetoothConnectionManager connectionManager = + new BluetoothConnectionManagerImpl(); Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); - JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor, - secureRandom, backoff, callback, MAX_LATENCY); + JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionManager, + ioExecutor, secureRandom, backoff, callback, MAX_LATENCY); eventBus.addListener(plugin); return plugin; } diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java index ffb0f6d4f..06b894f12 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java @@ -14,11 +14,15 @@ import javax.microedition.io.StreamConnection; class JavaBluetoothTransportConnection extends AbstractDuplexTransportConnection { + private final BluetoothConnectionManager connectionManager; private final StreamConnection stream; - JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) { + JavaBluetoothTransportConnection(Plugin plugin, + BluetoothConnectionManager connectionManager, + StreamConnection stream) { super(plugin); this.stream = stream; + this.connectionManager = connectionManager; } @Override @@ -33,6 +37,10 @@ class JavaBluetoothTransportConnection @Override protected void closeConnection(boolean exception) throws IOException { - stream.close(); + try { + stream.close(); + } finally { + connectionManager.connectionClosed(); + } } }