mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
13 Commits
alpha-1.4.
...
1712-detec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
173b6006c4 | ||
|
|
99edb893f7 | ||
|
|
f063feedd4 | ||
|
|
126f515760 | ||
|
|
e2b61483d6 | ||
|
|
9771825c45 | ||
|
|
e376744487 | ||
|
|
13cca9ca61 | ||
|
|
e464f9e7bd | ||
|
|
bd86ff2d5f | ||
|
|
bda3b2100a | ||
|
|
104a82aea9 | ||
|
|
d905451f48 |
@@ -67,7 +67,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||
BluetoothConnectionLimiter connectionLimiter =
|
||||
new BluetoothConnectionLimiterImpl();
|
||||
new BluetoothConnectionLimiterImpl(eventBus, clock);
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
||||
|
||||
@@ -50,7 +50,7 @@ class AndroidBluetoothTransportConnection
|
||||
try {
|
||||
socket.close();
|
||||
} finally {
|
||||
connectionLimiter.connectionClosed(this);
|
||||
connectionLimiter.connectionClosed(this, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.sync;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -11,9 +12,9 @@ public interface SyncSessionFactory {
|
||||
|
||||
SyncSession createIncomingSession(ContactId c, InputStream in);
|
||||
|
||||
SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency,
|
||||
StreamWriter streamWriter);
|
||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
int maxLatency, StreamWriter streamWriter);
|
||||
|
||||
SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
|
||||
int maxIdleTime, StreamWriter streamWriter);
|
||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||
int maxLatency, int maxIdleTime, StreamWriter streamWriter);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.briarproject.bramble.api.sync.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when all sync connections using a given
|
||||
* transport should be closed.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class CloseSyncConnectionsEvent extends Event {
|
||||
|
||||
private final TransportId transportId;
|
||||
|
||||
public CloseSyncConnectionsEvent(TransportId transportId) {
|
||||
this.transportId = transportId;
|
||||
}
|
||||
|
||||
public TransportId getTransportId() {
|
||||
return transportId;
|
||||
}
|
||||
}
|
||||
@@ -130,8 +130,8 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
TransportConnectionWriter w) throws IOException {
|
||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||
w.getOutputStream(), ctx);
|
||||
ContactId c = requireNonNull(ctx.getContactId());
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
return syncSessionFactory.createSimplexOutgoingSession(
|
||||
requireNonNull(ctx.getContactId()), ctx.getTransportId(),
|
||||
w.getMaxLatency(), streamWriter);
|
||||
}
|
||||
|
||||
@@ -139,8 +139,8 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
TransportConnectionWriter w) throws IOException {
|
||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||
w.getOutputStream(), ctx);
|
||||
ContactId c = requireNonNull(ctx.getContactId());
|
||||
return syncSessionFactory.createDuplexOutgoingSession(c,
|
||||
return syncSessionFactory.createDuplexOutgoingSession(
|
||||
requireNonNull(ctx.getContactId()), ctx.getTransportId(),
|
||||
w.getMaxLatency(), w.getMaxIdleTime(), streamWriter);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,30 @@ package org.briarproject.bramble.plugin.bluetooth;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
@NotNullByDefault
|
||||
interface BluetoothConnectionLimiter {
|
||||
|
||||
/**
|
||||
* How long a connection must remain open before it's considered stable.
|
||||
*/
|
||||
long STABILITY_PERIOD_MS = SECONDS.toMillis(90);
|
||||
|
||||
/**
|
||||
* The minimum interval between attempts to raise the connection limit.
|
||||
* This is longer than {@link #STABILITY_PERIOD_MS} so we don't start
|
||||
* another attempt before knowing the outcome of the last one.
|
||||
*/
|
||||
long MIN_ATTEMPT_INTERVAL_MS = MINUTES.toMillis(2);
|
||||
|
||||
/**
|
||||
* The maximum interval between attempts to raise the connection limit.
|
||||
*/
|
||||
long MAX_ATTEMPT_INTERVAL_MS = DAYS.toMillis(2);
|
||||
|
||||
/**
|
||||
* Informs the limiter that key agreement has started.
|
||||
*/
|
||||
@@ -23,12 +44,12 @@ interface BluetoothConnectionLimiter {
|
||||
boolean canOpenContactConnection();
|
||||
|
||||
/**
|
||||
* Informs the limiter that a contact connection has been opened. The
|
||||
* limiter may close the new connection if key agreement is in progress.
|
||||
* Informs the limiter that a contact connection has been opened.
|
||||
* <p/>
|
||||
* Returns false if the limiter has closed the new connection.
|
||||
* Returns true if the connection is allowed.
|
||||
*/
|
||||
boolean contactConnectionOpened(DuplexTransportConnection conn);
|
||||
boolean contactConnectionOpened(DuplexTransportConnection conn,
|
||||
boolean incoming);
|
||||
|
||||
/**
|
||||
* Informs the limiter that a key agreement connection has been opened.
|
||||
@@ -37,11 +58,13 @@ interface BluetoothConnectionLimiter {
|
||||
|
||||
/**
|
||||
* Informs the limiter that the given connection has been closed.
|
||||
*
|
||||
* @param exception True if the connection was closed due to an exception.
|
||||
*/
|
||||
void connectionClosed(DuplexTransportConnection conn);
|
||||
void connectionClosed(DuplexTransportConnection conn, boolean exception);
|
||||
|
||||
/**
|
||||
* Informs the limiter that all connections have been closed.
|
||||
* Informs the limiter that the Bluetooth adapter has been disabled.
|
||||
*/
|
||||
void allConnectionsClosed();
|
||||
void bluetoothDisabled();
|
||||
}
|
||||
|
||||
@@ -1,46 +1,59 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
|
||||
@NotNullByDefault
|
||||
@ThreadSafe
|
||||
class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothConnectionLimiterImpl.class.getName());
|
||||
getLogger(BluetoothConnectionLimiterImpl.class.getName());
|
||||
|
||||
private final EventBus eventBus;
|
||||
private final Clock clock;
|
||||
|
||||
private final Object lock = new Object();
|
||||
// The following are locking: lock
|
||||
private final LinkedList<DuplexTransportConnection> connections =
|
||||
new LinkedList<>();
|
||||
@GuardedBy("lock")
|
||||
private final List<ConnectionRecord> connections = new LinkedList<>();
|
||||
@GuardedBy("lock")
|
||||
private boolean keyAgreementInProgress = false;
|
||||
@GuardedBy("lock")
|
||||
private int connectionLimit = 1;
|
||||
@GuardedBy("lock")
|
||||
private long timeOfLastAttempt = 0,
|
||||
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
|
||||
|
||||
@Inject
|
||||
BluetoothConnectionLimiterImpl(EventBus eventBus, Clock clock) {
|
||||
this.eventBus = eventBus;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementStarted() {
|
||||
List<DuplexTransportConnection> close;
|
||||
synchronized (lock) {
|
||||
keyAgreementInProgress = true;
|
||||
close = new ArrayList<>(connections);
|
||||
connections.clear();
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Key agreement started, closing " + close.size() +
|
||||
" connections");
|
||||
}
|
||||
for (DuplexTransportConnection conn : close) tryToClose(conn);
|
||||
LOG.info("Key agreement started");
|
||||
eventBus.broadcast(new CloseSyncConnectionsEvent(ID));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,62 +68,128 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
||||
public boolean canOpenContactConnection() {
|
||||
synchronized (lock) {
|
||||
if (keyAgreementInProgress) {
|
||||
LOG.info("Can't open contact connection during key agreement");
|
||||
LOG.info("Refusing contact connection during key agreement");
|
||||
return false;
|
||||
} else {
|
||||
LOG.info("Can open contact connection");
|
||||
return true;
|
||||
long now = clock.currentTimeMillis();
|
||||
return isContactConnectionAllowedByLimit(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contactConnectionOpened(DuplexTransportConnection conn) {
|
||||
boolean accept = true;
|
||||
public boolean contactConnectionOpened(DuplexTransportConnection conn,
|
||||
boolean incoming) {
|
||||
synchronized (lock) {
|
||||
if (keyAgreementInProgress) {
|
||||
LOG.info("Refusing contact connection during key agreement");
|
||||
accept = false;
|
||||
return false;
|
||||
} else {
|
||||
LOG.info("Accepting contact connection");
|
||||
connections.add(conn);
|
||||
long now = clock.currentTimeMillis();
|
||||
if (incoming || isContactConnectionAllowedByLimit(now)) {
|
||||
connections.add(new ConnectionRecord(conn, now));
|
||||
if (!incoming && connections.size() > connectionLimit) {
|
||||
LOG.info("Attempting to raise connection limit");
|
||||
timeOfLastAttempt = now;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!accept) tryToClose(conn);
|
||||
return accept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
|
||||
synchronized (lock) {
|
||||
LOG.info("Accepting key agreement connection");
|
||||
connections.add(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
try {
|
||||
conn.getWriter().dispose(false);
|
||||
conn.getReader().dispose(false, false);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
connections.add(
|
||||
new ConnectionRecord(conn, clock.currentTimeMillis()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionClosed(DuplexTransportConnection conn) {
|
||||
public void connectionClosed(DuplexTransportConnection conn,
|
||||
boolean exception) {
|
||||
synchronized (lock) {
|
||||
connections.remove(conn);
|
||||
Iterator<ConnectionRecord> it = connections.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (it.next().connection == conn) {
|
||||
long now = clock.currentTimeMillis();
|
||||
if (exception) connectionFailed(now);
|
||||
else considerRaisingConnectionLimit(now);
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connection closed, " + connections.size() + " open");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allConnectionsClosed() {
|
||||
public void bluetoothDisabled() {
|
||||
synchronized (lock) {
|
||||
LOG.info("Bluetooth disabled");
|
||||
considerRaisingConnectionLimit(clock.currentTimeMillis());
|
||||
connections.clear();
|
||||
LOG.info("All connections closed");
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private boolean isContactConnectionAllowedByLimit(long now) {
|
||||
considerRaisingConnectionLimit(now);
|
||||
if (connections.size() > connectionLimit) {
|
||||
LOG.info("Refusing contact connection, above limit");
|
||||
return false;
|
||||
} else if (connections.size() < connectionLimit) {
|
||||
LOG.info("Allowing contact connection, below limit");
|
||||
return true;
|
||||
} else if (now - timeOfLastAttempt >= attemptInterval) {
|
||||
LOG.info("Allowing contact connection, at limit");
|
||||
return true;
|
||||
} else {
|
||||
LOG.info("Refusing contact connection, at limit");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void considerRaisingConnectionLimit(long now) {
|
||||
int stable = 0;
|
||||
for (ConnectionRecord rec : connections) {
|
||||
if (now - rec.timeOpened >= STABILITY_PERIOD_MS) stable++;
|
||||
}
|
||||
if (stable > connectionLimit) {
|
||||
LOG.info("Raising connection limit");
|
||||
connectionLimit = stable;
|
||||
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(stable + " connections are stable, limit is "
|
||||
+ connectionLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void connectionFailed(long now) {
|
||||
if (connections.size() > connectionLimit &&
|
||||
now - timeOfLastAttempt < STABILITY_PERIOD_MS) {
|
||||
LOG.info("Connection failed above limit, increasing interval");
|
||||
attemptInterval = min(attemptInterval * 2, MAX_ATTEMPT_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ConnectionRecord {
|
||||
|
||||
private final DuplexTransportConnection connection;
|
||||
private final long timeOpened;
|
||||
|
||||
private ConnectionRecord(DuplexTransportConnection connection,
|
||||
long timeOpened) {
|
||||
this.connection = connection;
|
||||
this.timeOpened = timeOpened;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
void onAdapterDisabled() {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
connectionLimiter.allConnectionsClosed();
|
||||
connectionLimiter.bluetoothDisabled();
|
||||
callback.transportDisabled();
|
||||
}
|
||||
|
||||
@@ -232,14 +232,25 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
return;
|
||||
}
|
||||
LOG.info("Connection received");
|
||||
if (connectionLimiter.contactConnectionOpened(conn)) {
|
||||
if (connectionLimiter.contactConnectionOpened(conn, true)) {
|
||||
backoff.reset();
|
||||
callback.handleConnection(conn);
|
||||
} else {
|
||||
tryToClose(conn);
|
||||
}
|
||||
if (!running) return;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
try {
|
||||
conn.getWriter().dispose(false);
|
||||
conn.getReader().dispose(false, false);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
@@ -328,7 +339,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
if (isNullOrEmpty(uuid)) return null;
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn == null) return null;
|
||||
return connectionLimiter.contactConnectionOpened(conn) ? conn : null;
|
||||
if (connectionLimiter.contactConnectionOpened(conn, false)) {
|
||||
return conn;
|
||||
} else {
|
||||
tryToClose(conn);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
@@ -18,6 +19,7 @@ import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.Versions;
|
||||
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
||||
@@ -71,6 +73,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final EventBus eventBus;
|
||||
private final Clock clock;
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final int maxLatency, maxIdleTime;
|
||||
private final StreamWriter streamWriter;
|
||||
private final SyncRecordWriter recordWriter;
|
||||
@@ -86,14 +89,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
|
||||
int maxIdleTime, StreamWriter streamWriter,
|
||||
SyncRecordWriter recordWriter) {
|
||||
EventBus eventBus, Clock clock, ContactId contactId,
|
||||
TransportId transportId, int maxLatency, int maxIdleTime,
|
||||
StreamWriter streamWriter, SyncRecordWriter recordWriter) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
this.clock = clock;
|
||||
this.contactId = contactId;
|
||||
this.transportId = transportId;
|
||||
this.maxLatency = maxLatency;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
this.streamWriter = streamWriter;
|
||||
@@ -223,6 +227,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
} else if (e instanceof CloseSyncConnectionsEvent) {
|
||||
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
|
||||
if (c.getTransportId().equals(transportId)) interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,13 @@ import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.Versions;
|
||||
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -56,6 +58,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final Executor dbExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final ContactId contactId;
|
||||
private final TransportId transportId;
|
||||
private final int maxLatency;
|
||||
private final StreamWriter streamWriter;
|
||||
private final SyncRecordWriter recordWriter;
|
||||
@@ -65,12 +68,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, ContactId contactId, int maxLatency,
|
||||
StreamWriter streamWriter, SyncRecordWriter recordWriter) {
|
||||
EventBus eventBus, ContactId contactId, TransportId transportId,
|
||||
int maxLatency, StreamWriter streamWriter,
|
||||
SyncRecordWriter recordWriter) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
this.contactId = contactId;
|
||||
this.transportId = transportId;
|
||||
this.maxLatency = maxLatency;
|
||||
this.streamWriter = streamWriter;
|
||||
this.recordWriter = recordWriter;
|
||||
@@ -123,6 +128,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
} else if (e instanceof CloseSyncConnectionsEvent) {
|
||||
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
|
||||
if (c.getTransportId().equals(transportId)) interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||
@@ -53,22 +54,23 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createSimplexOutgoingSession(ContactId c,
|
||||
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
int maxLatency, StreamWriter streamWriter) {
|
||||
OutputStream out = streamWriter.getOutputStream();
|
||||
SyncRecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(out);
|
||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
|
||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
||||
maxLatency, streamWriter, recordWriter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
|
||||
int maxIdleTime, StreamWriter streamWriter) {
|
||||
public SyncSession createDuplexOutgoingSession(ContactId c,
|
||||
TransportId t, int maxLatency, int maxIdleTime,
|
||||
StreamWriter streamWriter) {
|
||||
OutputStream out = streamWriter.getOutputStream();
|
||||
SyncRecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(out);
|
||||
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
|
||||
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, t,
|
||||
maxLatency, maxIdleTime, streamWriter, recordWriter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.SettableClock;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.MIN_ATTEMPT_INTERVAL_MS;
|
||||
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.STABILITY_PERIOD_MS;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
|
||||
private final DuplexTransportConnection conn1 =
|
||||
context.mock(DuplexTransportConnection.class, "conn1");
|
||||
private final DuplexTransportConnection conn2 =
|
||||
context.mock(DuplexTransportConnection.class, "conn2");
|
||||
private final DuplexTransportConnection conn3 =
|
||||
context.mock(DuplexTransportConnection.class, "conn3");
|
||||
|
||||
private final long now = System.currentTimeMillis();
|
||||
|
||||
private AtomicLong time;
|
||||
private BluetoothConnectionLimiter limiter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
time = new AtomicLong(now);
|
||||
Clock clock = new SettableClock(time);
|
||||
limiter = new BluetoothConnectionLimiterImpl(eventBus, clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLimiterDoesNotAllowContactConnectionsDuringKeyAgreement() {
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
|
||||
expectCloseSyncConnectionsEvent();
|
||||
limiter.keyAgreementStarted();
|
||||
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
|
||||
limiter.keyAgreementEnded();
|
||||
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLimiterAllowsAttemptToRaiseLimitAtStartup() {
|
||||
// First outgoing connection is allowed - we're below the limit of 1
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn1, false));
|
||||
|
||||
// Second outgoing connection is allowed - it's time to try raising
|
||||
// the limit to 2
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn2, false));
|
||||
|
||||
// Third outgoing connection is not allowed - we're above the limit of 1
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLimiterAllowsThirdConnectionAfterFirstTwoAreClosed() {
|
||||
// First outgoing connection is allowed - we're below the limit of 1
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn1, false));
|
||||
|
||||
// Second outgoing connection is allowed - it's time to try raising
|
||||
// the limit to 2
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn2, false));
|
||||
|
||||
// Third outgoing connection is not allowed - we're above the limit of 1
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
|
||||
// Close the first connection
|
||||
limiter.connectionClosed(conn1, false);
|
||||
|
||||
// Third outgoing connection is not allowed - we're at the limit of 1
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
|
||||
// Close the second connection
|
||||
limiter.connectionClosed(conn2, false);
|
||||
|
||||
// Third outgoing connection is allowed - we're below the limit of 1
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn3, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLimiterRaisesLimitWhenConnectionsAreStable() {
|
||||
// First outgoing connection is allowed - we're below the limit of 1
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn1, false));
|
||||
|
||||
// Second outgoing connection is allowed - it's time to try raising
|
||||
// the limit to 2
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn2, false));
|
||||
|
||||
// Third outgoing connection is not allowed - we're above the limit of 1
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
|
||||
// Time passes
|
||||
time.set(now + STABILITY_PERIOD_MS);
|
||||
|
||||
// Third outgoing connection is still not allowed - first two are now
|
||||
// stable so limit is raised to 2, but we're already at the new limit
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
|
||||
// Time passes
|
||||
time.set(now + MIN_ATTEMPT_INTERVAL_MS);
|
||||
|
||||
// Third outgoing connection is allowed - it's time to try raising
|
||||
// the limit to 3
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn3, false));
|
||||
|
||||
// Fourth outgoing connection is not allowed - we're above the limit
|
||||
// of 2
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLimiterIncreasesIntervalWhenConnectionFailsAboveLimit() {
|
||||
// First outgoing connection is allowed - we're below the limit of 1
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn1, false));
|
||||
|
||||
// Time passes
|
||||
time.set(now + 1);
|
||||
|
||||
// Second outgoing connection is allowed - it's time to try raising
|
||||
// the limit to 2
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn2, false));
|
||||
|
||||
// Time passes - the first connection is stable, the second isn't
|
||||
time.set(now + STABILITY_PERIOD_MS);
|
||||
|
||||
// First connection fails. The second connection isn't stable yet, so
|
||||
// the limiter considers this a failed attempt and doubles the interval
|
||||
// between attempts
|
||||
limiter.connectionClosed(conn1, true);
|
||||
|
||||
// Third outgoing connection is not allowed - we're still at the limit
|
||||
// of 1
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
|
||||
// Time passes - nearly time for the second attempt
|
||||
time.set(now + MIN_ATTEMPT_INTERVAL_MS * 2);
|
||||
|
||||
// Third outgoing connection is not allowed - we're still at the limit
|
||||
// of 1
|
||||
assertFalse(limiter.canOpenContactConnection());
|
||||
|
||||
// Time passes - now it's time for the second attempt
|
||||
time.set(now + 1 + MIN_ATTEMPT_INTERVAL_MS * 2);
|
||||
|
||||
// Third outgoing connection is allowed - it's time to try raising the
|
||||
// limit to 2 again
|
||||
assertTrue(limiter.canOpenContactConnection());
|
||||
assertTrue(limiter.contactConnectionOpened(conn3, false));
|
||||
}
|
||||
|
||||
private void expectCloseSyncConnectionsEvent() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
CloseSyncConnectionsEvent.class)));
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
@@ -23,6 +24,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -36,14 +38,15 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor dbExecutor = new ImmediateExecutor();
|
||||
private final ContactId contactId = getContactId();
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final Message message = getMessage(new GroupId(getRandomId()));
|
||||
private final MessageId messageId = message.getId();
|
||||
|
||||
@Test
|
||||
public void testNothingToSend() throws Exception {
|
||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||
dbExecutor, eventBus, contactId, MAX_LATENCY, streamWriter,
|
||||
recordWriter);
|
||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
||||
streamWriter, recordWriter);
|
||||
Transaction noAckTxn = new Transaction(null, false);
|
||||
Transaction noMsgTxn = new Transaction(null, false);
|
||||
|
||||
@@ -76,8 +79,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
||||
public void testSomethingToSend() throws Exception {
|
||||
Ack ack = new Ack(singletonList(messageId));
|
||||
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||
dbExecutor, eventBus, contactId, MAX_LATENCY, streamWriter,
|
||||
recordWriter);
|
||||
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
|
||||
streamWriter, recordWriter);
|
||||
Transaction ackTxn = new Transaction(null, false);
|
||||
Transaction noAckTxn = new Transaction(null, false);
|
||||
Transaction msgTxn = new Transaction(null, false);
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||
@@ -32,10 +33,11 @@ public class DesktopPluginModule extends PluginModule {
|
||||
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
|
||||
SecureRandom random, BackoffFactory backoffFactory,
|
||||
ReliabilityLayerFactory reliabilityFactory,
|
||||
ShutdownManager shutdownManager, EventBus eventBus,
|
||||
ShutdownManager shutdownManager, EventBus eventBus, Clock clock,
|
||||
TimeoutMonitor timeoutMonitor) {
|
||||
DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory(
|
||||
ioExecutor, random, eventBus, timeoutMonitor, backoffFactory);
|
||||
ioExecutor, random, eventBus, clock, timeoutMonitor,
|
||||
backoffFactory);
|
||||
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
|
||||
reliabilityFactory);
|
||||
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -30,15 +31,17 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
private final Executor ioExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final EventBus eventBus;
|
||||
private final Clock clock;
|
||||
private final TimeoutMonitor timeoutMonitor;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public JavaBluetoothPluginFactory(Executor ioExecutor,
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
SecureRandom secureRandom, EventBus eventBus, Clock clock,
|
||||
TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.eventBus = eventBus;
|
||||
this.clock = clock;
|
||||
this.timeoutMonitor = timeoutMonitor;
|
||||
this.backoffFactory = backoffFactory;
|
||||
}
|
||||
@@ -56,7 +59,7 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||
BluetoothConnectionLimiter connectionLimiter =
|
||||
new BluetoothConnectionLimiterImpl();
|
||||
new BluetoothConnectionLimiterImpl(eventBus, clock);
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
|
||||
|
||||
@@ -45,7 +45,7 @@ class JavaBluetoothTransportConnection
|
||||
try {
|
||||
stream.close();
|
||||
} finally {
|
||||
connectionLimiter.connectionClosed(this);
|
||||
connectionLimiter.connectionClosed(this, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user