Merge branch '1712-detect-dead-bluetooth-connections' into 'master'

Detect and close dead Bluetooth connections

See merge request briar/briar!1246
This commit is contained in:
Torsten Grote
2020-05-11 15:55:07 +00:00
15 changed files with 460 additions and 48 deletions

View File

@@ -9,6 +9,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -76,11 +77,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private volatile BluetoothAdapter adapter = null; private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, AndroidExecutor androidExecutor, TimeoutMonitor timeoutMonitor, Executor ioExecutor,
Context appContext, SecureRandom secureRandom, Clock clock, SecureRandom secureRandom, AndroidExecutor androidExecutor,
Backoff backoff, PluginCallback callback, int maxLatency) { Context appContext, Clock clock, Backoff backoff,
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback, PluginCallback callback, int maxLatency, int maxIdleTime) {
maxLatency); super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime);
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.clock = clock; this.clock = clock;
@@ -172,9 +174,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return wrapSocket(ss.accept()); return wrapSocket(ss.accept());
} }
private DuplexTransportConnection wrapSocket(BluetoothSocket s) { private DuplexTransportConnection wrapSocket(BluetoothSocket s)
return new AndroidBluetoothTransportConnection(this, throws IOException {
connectionLimiter, s); return new AndroidBluetoothTransportConnection(this, connectionLimiter,
timeoutMonitor, s);
} }
@Override @Override

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.content.Context; import android.content.Context;
import org.briarproject.bramble.api.event.EventBus; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory; 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 { public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds 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 MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
@@ -35,18 +37,20 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock; private final Clock clock;
private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor, public AndroidBluetoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext, AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, Clock clock, SecureRandom secureRandom, EventBus eventBus, Clock clock,
BackoffFactory backoffFactory) { TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock; this.clock = clock;
this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
} }
@@ -67,8 +71,9 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, ioExecutor, androidExecutor, appContext, connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
secureRandom, clock, backoff, callback, MAX_LATENCY); androidExecutor, appContext, clock, backoff,
callback, MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
@@ -17,22 +18,26 @@ import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress
class AndroidBluetoothTransportConnection class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection { extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager; private final BluetoothConnectionLimiter connectionLimiter;
private final BluetoothSocket socket; private final BluetoothSocket socket;
private final InputStream in;
AndroidBluetoothTransportConnection(Plugin plugin, AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager, BluetoothConnectionLimiter connectionLimiter,
BluetoothSocket socket) { TimeoutMonitor timeoutMonitor, BluetoothSocket socket)
throws IOException {
super(plugin); super(plugin);
this.connectionManager = connectionManager; this.connectionLimiter = connectionLimiter;
this.socket = socket; this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2);
String address = socket.getRemoteDevice().getAddress(); String address = socket.getRemoteDevice().getAddress();
if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address); if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address);
} }
@Override @Override
protected InputStream getInputStream() throws IOException { protected InputStream getInputStream() {
return socket.getInputStream(); return in;
} }
@Override @Override
@@ -45,7 +50,7 @@ class AndroidBluetoothTransportConnection
try { try {
socket.close(); socket.close();
} finally { } finally {
connectionManager.connectionClosed(this); connectionLimiter.connectionClosed(this);
} }
} }
} }

View File

@@ -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);
}

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.db.DatabaseModule; import org.briarproject.bramble.db.DatabaseModule;
import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule; import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
@@ -35,6 +36,7 @@ import dagger.Module;
DatabaseExecutorModule.class, DatabaseExecutorModule.class,
EventModule.class, EventModule.class,
IdentityModule.class, IdentityModule.class,
IoModule.class,
KeyAgreementModule.class, KeyAgreementModule.class,
LifecycleModule.class, LifecycleModule.class,
PluginModule.class, PluginModule.class,

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<TimeoutInputStream> 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<TimeoutInputStream> 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);
}
}
}
});
}
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener; 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.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
@@ -60,12 +61,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
getLogger(BluetoothPlugin.class.getName()); getLogger(BluetoothPlugin.class.getName());
final BluetoothConnectionLimiter connectionLimiter; final BluetoothConnectionLimiter connectionLimiter;
final TimeoutMonitor timeoutMonitor;
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final Backoff backoff; private final Backoff backoff;
private final PluginCallback callback; private final PluginCallback callback;
private final int maxLatency; private final int maxLatency, maxIdleTime;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false, contactConnections = false; private volatile boolean running = false, contactConnections = false;
@@ -105,14 +107,17 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
abstract DuplexTransportConnection discoverAndConnect(String uuid); abstract DuplexTransportConnection discoverAndConnect(String uuid);
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, SecureRandom secureRandom, TimeoutMonitor timeoutMonitor, Executor ioExecutor,
Backoff backoff, PluginCallback callback, int maxLatency) { SecureRandom secureRandom, Backoff backoff,
PluginCallback callback, int maxLatency, int maxIdleTime) {
this.connectionLimiter = connectionLimiter; this.connectionLimiter = connectionLimiter;
this.timeoutMonitor = timeoutMonitor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.backoff = backoff; this.backoff = backoff;
this.callback = callback; this.callback = callback;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
} }
void onAdapterEnabled() { void onAdapterEnabled() {
@@ -141,8 +146,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public int getMaxIdleTime() { public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives return maxIdleTime;
return Integer.MAX_VALUE;
} }
@Override @Override

View File

@@ -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);
}
}
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus; 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.IoExecutor;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -31,10 +32,10 @@ public class DesktopPluginModule extends PluginModule {
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor, PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
SecureRandom random, BackoffFactory backoffFactory, SecureRandom random, BackoffFactory backoffFactory,
ReliabilityLayerFactory reliabilityFactory, ReliabilityLayerFactory reliabilityFactory,
ShutdownManager shutdownManager, EventBus eventBus) { ShutdownManager shutdownManager, EventBus eventBus,
DuplexPluginFactory bluetooth = TimeoutMonitor timeoutMonitor) {
new JavaBluetoothPluginFactory(ioExecutor, random, eventBus, DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory(
backoffFactory); ioExecutor, random, eventBus, timeoutMonitor, backoffFactory);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory); reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.bluetooth; 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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -34,10 +35,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
private volatile LocalDevice localDevice = null; private volatile LocalDevice localDevice = null;
JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager, JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager,
Executor ioExecutor, SecureRandom secureRandom, TimeoutMonitor timeoutMonitor, Executor ioExecutor,
Backoff backoff, PluginCallback callback, int maxLatency) { SecureRandom secureRandom, Backoff backoff,
super(connectionManager, ioExecutor, secureRandom, backoff, callback, PluginCallback callback, int maxLatency, int maxIdleTime) {
maxLatency); super(connectionManager, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime);
} }
@Override @Override
@@ -119,7 +121,9 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM"; return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
} }
private DuplexTransportConnection wrapSocket(StreamConnection s) { private DuplexTransportConnection wrapSocket(StreamConnection s)
return new JavaBluetoothTransportConnection(this, connectionLimiter, s); throws IOException {
return new JavaBluetoothTransportConnection(this, connectionLimiter,
timeoutMonitor, s);
} }
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.plugin.bluetooth; package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.event.EventBus; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory; 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 { public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds 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 MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final BackoffFactory backoffFactory;
private final EventBus eventBus; private final EventBus eventBus;
private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory;
public JavaBluetoothPluginFactory(Executor ioExecutor, public JavaBluetoothPluginFactory(Executor ioExecutor,
SecureRandom secureRandom, EventBus eventBus, SecureRandom secureRandom, EventBus eventBus,
BackoffFactory backoffFactory) { TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.backoffFactory = backoffFactory;
this.eventBus = eventBus; this.eventBus = eventBus;
this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory;
} }
@Override @Override
@@ -56,7 +60,8 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter, JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
ioExecutor, secureRandom, backoff, callback, MAX_LATENCY); timeoutMonitor, ioExecutor, secureRandom, backoff, callback,
MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.bluetooth; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
@@ -14,20 +15,24 @@ import javax.microedition.io.StreamConnection;
class JavaBluetoothTransportConnection class JavaBluetoothTransportConnection
extends AbstractDuplexTransportConnection { extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager; private final BluetoothConnectionLimiter connectionLimiter;
private final StreamConnection stream; private final StreamConnection stream;
private final InputStream in;
JavaBluetoothTransportConnection(Plugin plugin, JavaBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager, BluetoothConnectionLimiter connectionLimiter,
StreamConnection stream) { TimeoutMonitor timeoutMonitor,
StreamConnection stream) throws IOException {
super(plugin); super(plugin);
this.connectionLimiter = connectionLimiter;
this.stream = stream; this.stream = stream;
this.connectionManager = connectionManager; in = timeoutMonitor.createTimeoutInputStream(
stream.openInputStream(), plugin.getMaxIdleTime() * 2);
} }
@Override @Override
protected InputStream getInputStream() throws IOException { protected InputStream getInputStream() {
return stream.openInputStream(); return in;
} }
@Override @Override
@@ -40,7 +45,7 @@ class JavaBluetoothTransportConnection
try { try {
stream.close(); stream.close();
} finally { } finally {
connectionManager.connectionClosed(this); connectionLimiter.connectionClosed(this);
} }
} }
} }

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus; 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.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
@@ -122,11 +123,12 @@ public class AppModule {
LocationUtils locationUtils, EventBus eventBus, LocationUtils locationUtils, EventBus eventBus,
ResourceProvider resourceProvider, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Clock clock) { BatteryManager batteryManager, Clock clock,
TimeoutMonitor timeoutMonitor) {
Context appContext = app.getApplicationContext(); Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = DuplexPluginFactory bluetooth = new AndroidBluetoothPluginFactory(
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, ioExecutor, androidExecutor, appContext, random, eventBus,
appContext, random, eventBus, clock, backoffFactory); clock, timeoutMonitor, backoffFactory);
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor, DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
scheduler, appContext, networkManager, locationUtils, eventBus, scheduler, appContext, networkManager, locationUtils, eventBus,
torSocketFactory, backoffFactory, resourceProvider, torSocketFactory, backoffFactory, resourceProvider,