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 f43812ad5..9b121f09d 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 @@ -9,6 +9,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; @@ -76,11 +77,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { private volatile BluetoothAdapter adapter = null; AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, - Executor ioExecutor, AndroidExecutor androidExecutor, - Context appContext, SecureRandom secureRandom, Clock clock, - Backoff backoff, PluginCallback callback, int maxLatency) { - super(connectionLimiter, ioExecutor, secureRandom, backoff, callback, - maxLatency); + TimeoutMonitor timeoutMonitor, Executor ioExecutor, + SecureRandom secureRandom, AndroidExecutor androidExecutor, + Context appContext, Clock clock, Backoff backoff, + PluginCallback callback, int maxLatency, int maxIdleTime) { + super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom, + backoff, callback, maxLatency, maxIdleTime); this.androidExecutor = androidExecutor; this.appContext = appContext; this.clock = clock; @@ -172,9 +174,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { return wrapSocket(ss.accept()); } - private DuplexTransportConnection wrapSocket(BluetoothSocket s) { - return new AndroidBluetoothTransportConnection(this, - connectionLimiter, s); + private DuplexTransportConnection wrapSocket(BluetoothSocket s) + throws IOException { + return new AndroidBluetoothTransportConnection(this, connectionLimiter, + timeoutMonitor, 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 6dd5c094f..bbf2e2744 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 @@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth; import android.content.Context; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.BackoffFactory; @@ -25,6 +26,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { private static final int MAX_LATENCY = 30 * 1000; // 30 seconds + private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final double BACKOFF_BASE = 1.2; @@ -35,18 +37,20 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { private final SecureRandom secureRandom; private final EventBus eventBus; private final Clock clock; + private final TimeoutMonitor timeoutMonitor; private final BackoffFactory backoffFactory; public AndroidBluetoothPluginFactory(Executor ioExecutor, AndroidExecutor androidExecutor, Context appContext, SecureRandom secureRandom, EventBus eventBus, Clock clock, - BackoffFactory backoffFactory) { + TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; this.androidExecutor = androidExecutor; this.appContext = appContext; this.secureRandom = secureRandom; this.eventBus = eventBus; this.clock = clock; + this.timeoutMonitor = timeoutMonitor; this.backoffFactory = backoffFactory; } @@ -67,8 +71,9 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( - connectionLimiter, ioExecutor, androidExecutor, appContext, - secureRandom, clock, backoff, callback, MAX_LATENCY); + connectionLimiter, timeoutMonitor, ioExecutor, secureRandom, + androidExecutor, appContext, clock, backoff, + callback, MAX_LATENCY, MAX_IDLE_TIME); 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 226ad0330..bf6b3d69f 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 @@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.bluetooth; import android.bluetooth.BluetoothSocket; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; @@ -17,22 +18,26 @@ import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress class AndroidBluetoothTransportConnection extends AbstractDuplexTransportConnection { - private final BluetoothConnectionLimiter connectionManager; + private final BluetoothConnectionLimiter connectionLimiter; private final BluetoothSocket socket; + private final InputStream in; AndroidBluetoothTransportConnection(Plugin plugin, - BluetoothConnectionLimiter connectionManager, - BluetoothSocket socket) { + BluetoothConnectionLimiter connectionLimiter, + TimeoutMonitor timeoutMonitor, BluetoothSocket socket) + throws IOException { super(plugin); - this.connectionManager = connectionManager; + this.connectionLimiter = connectionLimiter; this.socket = socket; + in = timeoutMonitor.createTimeoutInputStream( + socket.getInputStream(), plugin.getMaxIdleTime() * 2); String address = socket.getRemoteDevice().getAddress(); if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address); } @Override - protected InputStream getInputStream() throws IOException { - return socket.getInputStream(); + protected InputStream getInputStream() { + return in; } @Override @@ -45,7 +50,7 @@ class AndroidBluetoothTransportConnection try { socket.close(); } finally { - connectionManager.connectionClosed(this); + connectionLimiter.connectionClosed(this); } } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java b/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java new file mode 100644 index 000000000..92d440946 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java @@ -0,0 +1,15 @@ +package org.briarproject.bramble.api.io; + +import java.io.InputStream; + +public interface TimeoutMonitor { + + /** + * Returns an {@link InputStream} that wraps the given stream and allows + * read timeouts to be detected. + * + * @param timeoutMs The read timeout in milliseconds. Timeouts will be + * detected eventually but are not guaranteed to be detected immediately. + */ + InputStream createTimeoutInputStream(InputStream in, long timeoutMs); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java index 6876c587a..d95b07e0a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.db.DatabaseExecutorModule; import org.briarproject.bramble.db.DatabaseModule; import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.identity.IdentityModule; +import org.briarproject.bramble.io.IoModule; import org.briarproject.bramble.keyagreement.KeyAgreementModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.plugin.PluginModule; @@ -35,6 +36,7 @@ import dagger.Module; DatabaseExecutorModule.class, EventModule.class, IdentityModule.class, + IoModule.class, KeyAgreementModule.class, LifecycleModule.class, PluginModule.class, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/IoModule.java b/bramble-core/src/main/java/org/briarproject/bramble/io/IoModule.java new file mode 100644 index 000000000..f8aed8ae3 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/IoModule.java @@ -0,0 +1,18 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.api.io.TimeoutMonitor; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class IoModule { + + @Provides + @Singleton + TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) { + return timeoutMonitor; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java new file mode 100644 index 000000000..1a433254f --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java @@ -0,0 +1,104 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; + +import java.io.IOException; +import java.io.InputStream; + +import javax.annotation.concurrent.GuardedBy; + +@NotNullByDefault +class TimeoutInputStream extends InputStream { + + private final Clock clock; + private final InputStream in; + private final long timeoutMs; + private final CloseListener listener; + private final Object lock = new Object(); + @GuardedBy("lock") + private long readStartedMs = -1; + + TimeoutInputStream(Clock clock, InputStream in, long timeoutMs, + CloseListener listener) { + this.clock = clock; + this.in = in; + this.timeoutMs = timeoutMs; + this.listener = listener; + } + + @Override + public int read() throws IOException { + synchronized (lock) { + readStartedMs = clock.currentTimeMillis(); + } + int input = in.read(); + synchronized (lock) { + readStartedMs = -1; + } + return input; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + synchronized (lock) { + readStartedMs = clock.currentTimeMillis(); + } + int read = in.read(b, off, len); + synchronized (lock) { + readStartedMs = -1; + } + return read; + } + + @Override + public void close() throws IOException { + try { + in.close(); + } finally { + listener.onClose(this); + } + } + + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public void mark(int readlimit) { + in.mark(readlimit); + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + @Override + public void reset() throws IOException { + in.reset(); + } + + @Override + public long skip(long n) throws IOException { + return in.skip(n); + } + + boolean hasTimedOut() { + synchronized (lock) { + return readStartedMs != -1 && + clock.currentTimeMillis() - readStartedMs > timeoutMs; + } + } + + interface CloseListener { + + void onClose(TimeoutInputStream closed); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java new file mode 100644 index 000000000..eb68cb3a4 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java @@ -0,0 +1,96 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.api.io.TimeoutMonitor; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.system.Scheduler; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Logger; + +import javax.annotation.concurrent.GuardedBy; +import javax.inject.Inject; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +class TimeoutMonitorImpl implements TimeoutMonitor { + + private static final Logger LOG = + getLogger(TimeoutMonitorImpl.class.getName()); + + private static final long CHECK_INTERVAL_MS = SECONDS.toMillis(10); + + private final ScheduledExecutorService scheduler; + private final Executor ioExecutor; + private final Clock clock; + private final Object lock = new Object(); + @GuardedBy("lock") + private final List streams = new ArrayList<>(); + + @GuardedBy("lock") + private Future task = null; + + @Inject + TimeoutMonitorImpl(@Scheduler ScheduledExecutorService scheduler, + @IoExecutor Executor ioExecutor, Clock clock) { + this.scheduler = scheduler; + this.ioExecutor = ioExecutor; + this.clock = clock; + } + + @Override + public InputStream createTimeoutInputStream(InputStream in, + long timeoutMs) { + TimeoutInputStream stream = new TimeoutInputStream(clock, in, + timeoutMs, this::removeStream); + synchronized (lock) { + if (streams.isEmpty()) { + task = scheduler.scheduleWithFixedDelay(this::checkTimeouts, + CHECK_INTERVAL_MS, CHECK_INTERVAL_MS, MILLISECONDS); + } + streams.add(stream); + } + return stream; + } + + private void removeStream(TimeoutInputStream stream) { + Future toCancel = null; + synchronized (lock) { + if (streams.remove(stream) && streams.isEmpty()) { + toCancel = task; + task = null; + } + } + if (toCancel != null) toCancel.cancel(false); + } + + @Scheduler + private void checkTimeouts() { + ioExecutor.execute(() -> { + List snapshot; + synchronized (lock) { + snapshot = new ArrayList<>(streams); + } + for (TimeoutInputStream stream : snapshot) { + if (stream.hasTimedOut()) { + LOG.info("Input stream has timed out"); + try { + stream.close(); + } catch (IOException e) { + logException(LOG, INFO, e); + } + } + } + }); + } +} 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 350b0ad1d..9187c538b 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 @@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; @@ -60,12 +61,13 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { getLogger(BluetoothPlugin.class.getName()); final BluetoothConnectionLimiter connectionLimiter; + final TimeoutMonitor timeoutMonitor; private final Executor ioExecutor; private final SecureRandom secureRandom; private final Backoff backoff; private final PluginCallback callback; - private final int maxLatency; + private final int maxLatency, maxIdleTime; private final AtomicBoolean used = new AtomicBoolean(false); private volatile boolean running = false, contactConnections = false; @@ -105,14 +107,17 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { abstract DuplexTransportConnection discoverAndConnect(String uuid); BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, - Executor ioExecutor, SecureRandom secureRandom, - Backoff backoff, PluginCallback callback, int maxLatency) { + TimeoutMonitor timeoutMonitor, Executor ioExecutor, + SecureRandom secureRandom, Backoff backoff, + PluginCallback callback, int maxLatency, int maxIdleTime) { this.connectionLimiter = connectionLimiter; + this.timeoutMonitor = timeoutMonitor; this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; this.backoff = backoff; this.callback = callback; this.maxLatency = maxLatency; + this.maxIdleTime = maxIdleTime; } void onAdapterEnabled() { @@ -141,8 +146,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public int getMaxIdleTime() { - // Bluetooth detects dead connections so we don't need keepalives - return Integer.MAX_VALUE; + return maxIdleTime; } @Override diff --git a/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java b/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java new file mode 100644 index 000000000..3760210d2 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java @@ -0,0 +1,143 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.test.BrambleTestCase; +import org.briarproject.bramble.test.SettableClock; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TimeoutInputStreamTest extends BrambleTestCase { + + private static final long TIMEOUT_MS = MINUTES.toMillis(1); + + private final long now = System.currentTimeMillis(); + + private AtomicLong time; + private UnresponsiveInputStream in; + private AtomicBoolean listenerCalled; + private TimeoutInputStream stream; + private CountDownLatch readReturned; + + @Before + public void setUp() { + time = new AtomicLong(now); + in = new UnresponsiveInputStream(); + listenerCalled = new AtomicBoolean(false); + stream = new TimeoutInputStream(new SettableClock(time), in, + TIMEOUT_MS, stream -> listenerCalled.set(true)); + readReturned = new CountDownLatch(1); + } + + @Test + public void testTimeoutIsReportedIfReadDoesNotReturn() throws Exception { + startReading(); + try { + // The stream should not report a timeout + assertFalse(stream.hasTimedOut()); + + // Time passes + time.set(now + TIMEOUT_MS); + + // The stream still shouldn't report a timeout + assertFalse(stream.hasTimedOut()); + + // Time passes + time.set(now + TIMEOUT_MS + 1); + + // The stream should report a timeout + assertTrue(stream.hasTimedOut()); + + // The listener should not have been called yet + assertFalse(listenerCalled.get()); + + // Close the stream + stream.close(); + + // The listener should have been called + assertTrue(listenerCalled.get()); + } finally { + // Allow the read to return + in.readFinished.countDown(); + } + } + + @Test + public void testTimeoutIsNotReportedIfReadReturns() throws Exception { + startReading(); + try { + // The stream should not report a timeout + assertFalse(stream.hasTimedOut()); + + // Time passes + time.set(now + TIMEOUT_MS); + + // The stream still shouldn't report a timeout + assertFalse(stream.hasTimedOut()); + + // Allow the read to finish and wait for it to return + in.readFinished.countDown(); + readReturned.await(10, SECONDS); + + // Time passes + time.set(now + TIMEOUT_MS + 1); + + // The stream should not report a timeout as the read has returned + assertFalse(stream.hasTimedOut()); + + // The listener should not have been called yet + assertFalse(listenerCalled.get()); + + // Close the stream + stream.close(); + + // The listener should have been called + assertTrue(listenerCalled.get()); + } finally { + // Allow the read to return in case an assertion was thrown + in.readFinished.countDown(); + } + } + + private void startReading() throws Exception { + // Start a background thread to read from the unresponsive stream + new Thread(() -> { + try { + assertEquals(123, stream.read()); + readReturned.countDown(); + } catch (IOException e) { + fail(); + } + }).start(); + // Wait for the background thread to start reading + assertTrue(in.readStarted.await(10, SECONDS)); + } + + private class UnresponsiveInputStream extends InputStream { + + private final CountDownLatch readStarted = new CountDownLatch(1); + private final CountDownLatch readFinished = new CountDownLatch(1); + + @Override + public int read() throws IOException { + readStarted.countDown(); + try { + readFinished.await(); + return 123; + } catch (InterruptedException e) { + throw new IOException(e); + } + } + } +} diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java index 5b4cf7014..c00bd99d7 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.plugin; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -31,10 +32,10 @@ public class DesktopPluginModule extends PluginModule { PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor, SecureRandom random, BackoffFactory backoffFactory, ReliabilityLayerFactory reliabilityFactory, - ShutdownManager shutdownManager, EventBus eventBus) { - DuplexPluginFactory bluetooth = - new JavaBluetoothPluginFactory(ioExecutor, random, eventBus, - backoffFactory); + ShutdownManager shutdownManager, EventBus eventBus, + TimeoutMonitor timeoutMonitor) { + DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory( + ioExecutor, random, eventBus, timeoutMonitor, backoffFactory); DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, reliabilityFactory); DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java index 59a105f20..ff9b7ee79 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.plugin.bluetooth; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; @@ -34,10 +35,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin { private volatile LocalDevice localDevice = null; JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager, - Executor ioExecutor, SecureRandom secureRandom, - Backoff backoff, PluginCallback callback, int maxLatency) { - super(connectionManager, ioExecutor, secureRandom, backoff, callback, - maxLatency); + TimeoutMonitor timeoutMonitor, Executor ioExecutor, + SecureRandom secureRandom, Backoff backoff, + PluginCallback callback, int maxLatency, int maxIdleTime) { + super(connectionManager, timeoutMonitor, ioExecutor, secureRandom, + backoff, callback, maxLatency, maxIdleTime); } @Override @@ -119,7 +121,9 @@ class JavaBluetoothPlugin extends BluetoothPlugin { return "btspp://" + address + ":" + uuid + ";name=RFCOMM"; } - private DuplexTransportConnection wrapSocket(StreamConnection s) { - return new JavaBluetoothTransportConnection(this, connectionLimiter, s); + private DuplexTransportConnection wrapSocket(StreamConnection s) + throws IOException { + return new JavaBluetoothTransportConnection(this, connectionLimiter, + timeoutMonitor, s); } } diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java index a2ada859d..59fc25c86 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.plugin.bluetooth; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.BackoffFactory; @@ -21,22 +22,25 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; public class JavaBluetoothPluginFactory implements DuplexPluginFactory { private static final int MAX_LATENCY = 30 * 1000; // 30 seconds + private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final double BACKOFF_BASE = 1.2; private final Executor ioExecutor; private final SecureRandom secureRandom; - private final BackoffFactory backoffFactory; private final EventBus eventBus; + private final TimeoutMonitor timeoutMonitor; + private final BackoffFactory backoffFactory; public JavaBluetoothPluginFactory(Executor ioExecutor, SecureRandom secureRandom, EventBus eventBus, - BackoffFactory backoffFactory) { + TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; - this.backoffFactory = backoffFactory; this.eventBus = eventBus; + this.timeoutMonitor = timeoutMonitor; + this.backoffFactory = backoffFactory; } @Override @@ -56,7 +60,8 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter, - ioExecutor, secureRandom, backoff, callback, MAX_LATENCY); + timeoutMonitor, ioExecutor, secureRandom, backoff, callback, + MAX_LATENCY, MAX_IDLE_TIME); eventBus.addListener(plugin); return plugin; } diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java index 097c3c191..319d9af76 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.plugin.bluetooth; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; @@ -14,20 +15,24 @@ import javax.microedition.io.StreamConnection; class JavaBluetoothTransportConnection extends AbstractDuplexTransportConnection { - private final BluetoothConnectionLimiter connectionManager; + private final BluetoothConnectionLimiter connectionLimiter; private final StreamConnection stream; + private final InputStream in; JavaBluetoothTransportConnection(Plugin plugin, - BluetoothConnectionLimiter connectionManager, - StreamConnection stream) { + BluetoothConnectionLimiter connectionLimiter, + TimeoutMonitor timeoutMonitor, + StreamConnection stream) throws IOException { super(plugin); + this.connectionLimiter = connectionLimiter; this.stream = stream; - this.connectionManager = connectionManager; + in = timeoutMonitor.createTimeoutInputStream( + stream.openInputStream(), plugin.getMaxIdleTime() * 2); } @Override - protected InputStream getInputStream() throws IOException { - return stream.openInputStream(); + protected InputStream getInputStream() { + return in; } @Override @@ -40,7 +45,7 @@ class JavaBluetoothTransportConnection try { stream.close(); } finally { - connectionManager.connectionClosed(this); + connectionLimiter.connectionClosed(this); } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 07b37f2a8..b9e074897 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -14,6 +14,7 @@ import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.network.NetworkManager; @@ -122,11 +123,12 @@ public class AppModule { LocationUtils locationUtils, EventBus eventBus, ResourceProvider resourceProvider, CircumventionProvider circumventionProvider, - BatteryManager batteryManager, Clock clock) { + BatteryManager batteryManager, Clock clock, + TimeoutMonitor timeoutMonitor) { Context appContext = app.getApplicationContext(); - DuplexPluginFactory bluetooth = - new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, - appContext, random, eventBus, clock, backoffFactory); + DuplexPluginFactory bluetooth = new AndroidBluetoothPluginFactory( + ioExecutor, androidExecutor, appContext, random, eventBus, + clock, timeoutMonitor, backoffFactory); DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor, scheduler, appContext, networkManager, locationUtils, eventBus, torSocketFactory, backoffFactory, resourceProvider,