Compare commits

..

6 Commits

Author SHA1 Message Date
akwizgran
66d907d1b2 Add comment explaining relationship between constants. 2022-06-13 11:26:07 +01:00
akwizgran
7d336c98e4 Raise stability threshold and poll less often when network is stable. 2022-06-13 11:26:07 +01:00
akwizgran
963e510c3b Fix PollerImplTest expectations. 2022-06-13 11:26:07 +01:00
akwizgran
213d2f1da4 Disable and re-enable network if we can't reach our own HS. 2022-06-13 11:26:07 +01:00
akwizgran
3038b92dbc Poll less frequently when own hidden service is stable. 2022-06-13 11:26:06 +01:00
akwizgran
7cd3c2890b Remove backoff for polling Tor plugin. 2022-06-13 11:26:06 +01:00
206 changed files with 2188 additions and 9633 deletions

View File

@@ -1,10 +0,0 @@
Folder-Description:
===================
* `briar-*`: Specifically for the Briar app (Phone/Desktop/Headless)
* `bramble-*`: The protocol stack - not necessarily Briar-dependent
---
* `*-api`: public stuff that can be referenced from other packages and modules - mostly interfaces + a few utility classes
* `*-core`: implementations of api that are portable across Android/Desktop/Headless
* `*-java`: implementations of api that are specific to Desktop & Headless

View File

@@ -15,8 +15,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10411 versionCode 10408
versionName "1.4.11" versionName "1.4.8"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -18,7 +18,3 @@
-dontnote com.google.common.** -dontnote com.google.common.**
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl -dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
# Keep all Jackson-serialisable classes and their members
-keep interface com.fasterxml.jackson.databind.annotation.JsonSerialize
-keep @com.fasterxml.jackson.databind.annotation.JsonSerialize class * { *; }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble; package org.briarproject.bramble;
import org.briarproject.bramble.battery.AndroidBatteryModule; import org.briarproject.bramble.battery.AndroidBatteryModule;
import org.briarproject.bramble.io.DnsModule;
import org.briarproject.bramble.network.AndroidNetworkModule; import org.briarproject.bramble.network.AndroidNetworkModule;
import org.briarproject.bramble.plugin.tor.CircumventionModule; import org.briarproject.bramble.plugin.tor.CircumventionModule;
import org.briarproject.bramble.reporting.ReportingModule; import org.briarproject.bramble.reporting.ReportingModule;
@@ -19,7 +18,6 @@ import dagger.Module;
AndroidTaskSchedulerModule.class, AndroidTaskSchedulerModule.class,
AndroidWakefulIoExecutorModule.class, AndroidWakefulIoExecutorModule.class,
CircumventionModule.class, CircumventionModule.class,
DnsModule.class,
ReportingModule.class, ReportingModule.class,
SocksModule.class SocksModule.class
}) })

View File

@@ -86,8 +86,8 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
BluetoothConnectionFactory<BluetoothSocket> connectionFactory = BluetoothConnectionFactory<BluetoothSocket> connectionFactory =
new AndroidBluetoothConnectionFactory(connectionLimiter, new AndroidBluetoothConnectionFactory(connectionLimiter,
wakeLockManager, timeoutMonitor); wakeLockManager, timeoutMonitor);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, connectionFactory, ioExecutor, connectionLimiter, connectionFactory, ioExecutor,
wakefulIoExecutor, secureRandom, androidExecutor, app, wakefulIoExecutor, secureRandom, androidExecutor, app,

View File

@@ -61,8 +61,8 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor, AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
wakefulIoExecutor, app, backoff, callback, wakefulIoExecutor, app, backoff, callback,
MAX_LATENCY, MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_LATENCY, MAX_IDLE_TIME, CONNECTION_TIMEOUT);

View File

@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.system.AndroidWakeLock; import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager; import org.briarproject.bramble.api.system.AndroidWakeLockManager;
@@ -64,7 +63,6 @@ class AndroidTorPlugin extends TorPlugin {
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager, AndroidWakeLockManager wakeLockManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, PluginCallback callback,
String architecture, String architecture,
@@ -75,7 +73,7 @@ class AndroidTorPlugin extends TorPlugin {
int torControlPort) { int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils, super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider, torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff, circumventionProvider, batteryManager,
torRendezvousCrypto, callback, architecture, maxLatency, torRendezvousCrypto, callback, architecture, maxLatency,
maxIdleTime, torDirectory, torSocksPort, torControlPort); maxIdleTime, torDirectory, torSocksPort, torControlPort);
this.app = app; this.app = app;

View File

@@ -8,8 +8,6 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorControlPort; import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory; import org.briarproject.bramble.api.plugin.TorDirectory;
@@ -44,7 +42,6 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
LocationUtils locationUtils, LocationUtils locationUtils,
EventBus eventBus, EventBus eventBus,
SocketFactory torSocketFactory, SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
@@ -56,7 +53,7 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
Application app, Application app,
AndroidWakeLockManager wakeLockManager) { AndroidWakeLockManager wakeLockManager) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils, super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider, eventBus, torSocketFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto, circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort); torDirectory, torSocksPort, torControlPort);
this.app = app; this.app = app;
@@ -76,14 +73,14 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
} }
@Override @Override
TorPlugin createPluginInstance(Backoff backoff, TorPlugin createPluginInstance(TorRendezvousCrypto torRendezvousCrypto,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, PluginCallback callback,
String architecture) { String architecture) {
return new AndroidTorPlugin(ioExecutor, return new AndroidTorPlugin(ioExecutor,
wakefulIoExecutor, app, networkManager, locationUtils, wakefulIoExecutor, app, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider, torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, wakeLockManager, circumventionProvider, batteryManager, wakeLockManager,
backoff, torRendezvousCrypto, callback, architecture, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort, MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort); torControlPort);
} }

View File

@@ -5,7 +5,6 @@ targetCompatibility = 1.8
apply plugin: 'ru.vyarus.animalsniffer' apply plugin: 'ru.vyarus.animalsniffer'
apply plugin: 'witness' apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
apply plugin: 'checkstyle'
dependencies { dependencies {
implementation "com.google.dagger:dagger:$dagger_version" implementation "com.google.dagger:dagger:$dagger_version"

View File

@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
@NotNullByDefault @NotNullByDefault
public interface ConnectionManager { public interface ConnectionManager {
@@ -46,14 +45,6 @@ public interface ConnectionManager {
void manageOutgoingConnection(ContactId c, TransportId t, void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w); TransportConnectionWriter w);
/**
* Manages an outgoing connection to a contact via a mailbox. The IDs of
* any messages sent or acked are added to the given
* {@link OutgoingSessionRecord}.
*/
void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord);
/** /**
* Manages an outgoing connection to a contact over a duplex transport. * Manages an outgoing connection to a contact over a duplex transport.
*/ */

View File

@@ -126,11 +126,16 @@ public interface DatabaseComponent extends TransactionManager {
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/** /**
* Returns true if there are any acks to send to the given contact. * Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/> * <p/>
* Read-only. * Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/ */
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException; boolean containsAnythingToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
@@ -156,18 +161,6 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
boolean containsIdentity(Transaction txn, AuthorId a) throws DbException; boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
/**
* Returns true if there are any messages to send to the given contact
* over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsMessagesToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given pending contact. * Returns true if the database contains the given pending contact.
* <p/> * <p/>
@@ -283,13 +276,6 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
Group getGroup(Transaction txn, GroupId g) throws DbException; Group getGroup(Transaction txn, GroupId g) throws DbException;
/**
* Returns the ID of the group containing the given message.
* <p/>
* Read-only.
*/
GroupId getGroupId(Transaction txn, MessageId m) throws DbException;
/** /**
* Returns the metadata for the given group. * Returns the metadata for the given group.
* <p/> * <p/>
@@ -356,13 +342,13 @@ public interface DatabaseComponent extends TransactionManager {
Metadata query) throws DbException; Metadata query) throws DbException;
/** /**
* Returns the IDs of all messages received from the given contact that * Returns the IDs of some messages received from the given contact that
* need to be acknowledged. * need to be acknowledged, up to the given number of messages.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c) Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
throws DbException; int maxMessages) throws DbException;
/** /**
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
@@ -499,8 +485,6 @@ public interface DatabaseComponent extends TransactionManager {
* Returns the message with the given ID for transmission to the given * Returns the message with the given ID for transmission to the given
* contact over a transport with the given maximum latency. Returns null * contact over a transport with the given maximum latency. Returns null
* if the message is no longer visible to the contact. * if the message is no longer visible to the contact.
* <p/>
* Read-only if {@code markAsSent} is false.
* *
* @param markAsSent True if the message should be marked as sent. * @param markAsSent True if the message should be marked as sent.
* If false it can be marked as sent by calling * If false it can be marked as sent by calling
@@ -550,18 +534,15 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
long getNextCleanupDeadline(Transaction txn) throws DbException; long getNextCleanupDeadline(Transaction txn) throws DbException;
/** /*
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact over a transport with * message is due to be sent to the given contact. The returned value may
* the given latency. * be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
* <p> * no messages are scheduled to be sent.
* The returned value may be zero if a message is due to be sent
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
long getNextSendTime(Transaction txn, ContactId c, long maxLatency) long getNextSendTime(Transaction txn, ContactId c) throws DbException;
throws DbException;
/** /**
* Returns the pending contact with the given ID. * Returns the pending contact with the given ID.

View File

@@ -37,14 +37,8 @@ public interface LifecycleManager {
*/ */
enum LifecycleState { enum LifecycleState {
CREATED, STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
STARTING, RUNNING, STOPPING;
MIGRATING_DATABASE,
COMPACTING_DATABASE,
STARTING_SERVICES,
RUNNING,
STOPPING,
STOPPED;
public boolean isAfter(LifecycleState state) { public boolean isAfter(LifecycleState state) {
return ordinal() > state.ordinal(); return ordinal() > state.ordinal();

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List; import java.util.List;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
@@ -66,8 +65,4 @@ public interface MailboxConstants {
*/ */
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1); long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
/**
* The maximum latency of the mailbox transport in milliseconds.
*/
long MAX_LATENCY = DAYS.toMillis(14);
} }

View File

@@ -1,22 +1,19 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List; import java.util.List;
import java.util.TreeSet; import java.util.TreeSet;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD; import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD; import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
@NotNullByDefault class MailboxHelper {
public class MailboxHelper {
/** /**
* Returns the highest major version that both client and server support * Returns the highest major version that both client and server support
* or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old * or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old
* or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old. * or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old.
*/ */
public static int getHighestCommonMajorVersion( static int getHighestCommonMajorVersion(
List<MailboxVersion> client, List<MailboxVersion> server) { List<MailboxVersion> client, List<MailboxVersion> server) {
TreeSet<Integer> clientVersions = new TreeSet<>(); TreeSet<Integer> clientVersions = new TreeSet<>();
for (MailboxVersion version : client) { for (MailboxVersion version : client) {
@@ -35,13 +32,4 @@ public class MailboxHelper {
return API_SERVER_TOO_OLD; return API_SERVER_TOO_OLD;
} }
/**
* Returns true if a client and server with the given API versions can
* communicate with each other (ie, have any major API versions in common).
*/
public static boolean isClientCompatibleWithServer(
List<MailboxVersion> client, List<MailboxVersion> server) {
int common = getHighestCommonMajorVersion(client, server);
return common != API_CLIENT_TOO_OLD && common != API_SERVER_TOO_OLD;
}
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.api.mailbox;
import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonValue;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -33,7 +32,7 @@ public abstract class MailboxId extends UniqueId {
} }
try { try {
return fromHexString(token); return fromHexString(token);
} catch (FormatException e) { } catch (IllegalArgumentException e) {
throw new InvalidMailboxIdException(); throw new InvalidMailboxIdException();
} }
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import java.util.List; import java.util.List;
@@ -12,7 +11,7 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class MailboxProperties { public class MailboxProperties {
private final String onion; private final String baseUrl;
private final MailboxAuthToken authToken; private final MailboxAuthToken authToken;
private final boolean owner; private final boolean owner;
private final List<MailboxVersion> serverSupports; private final List<MailboxVersion> serverSupports;
@@ -24,9 +23,9 @@ public class MailboxProperties {
/** /**
* Constructor for properties used by the mailbox's owner. * Constructor for properties used by the mailbox's owner.
*/ */
public MailboxProperties(String onion, MailboxAuthToken authToken, public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports) { List<MailboxVersion> serverSupports) {
this.onion = onion; this.baseUrl = baseUrl;
this.authToken = authToken; this.authToken = authToken;
this.owner = true; this.owner = true;
this.serverSupports = serverSupports; this.serverSupports = serverSupports;
@@ -37,10 +36,10 @@ public class MailboxProperties {
/** /**
* Constructor for properties used by a contact of the mailbox's owner. * Constructor for properties used by a contact of the mailbox's owner.
*/ */
public MailboxProperties(String onion, MailboxAuthToken authToken, public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports, MailboxFolderId inboxId, List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
MailboxFolderId outboxId) { MailboxFolderId outboxId) {
this.onion = onion; this.baseUrl = baseUrl;
this.authToken = authToken; this.authToken = authToken;
this.owner = false; this.owner = false;
this.serverSupports = serverSupports; this.serverSupports = serverSupports;
@@ -48,11 +47,13 @@ public class MailboxProperties {
this.outboxId = outboxId; this.outboxId = outboxId;
} }
/** public String getBaseUrl() {
* Returns the onion address of the mailbox, excluding the .onion suffix. return baseUrl;
*/ }
public String getOnion() { public String getOnion() {
return onion; return baseUrl.replaceFirst("^http://", "")
.replaceFirst("\\.onion$", "");
} }
public MailboxAuthToken getAuthToken() { public MailboxAuthToken getAuthToken() {
@@ -76,23 +77,4 @@ public class MailboxProperties {
public MailboxFolderId getOutboxId() { public MailboxFolderId getOutboxId() {
return outboxId; return outboxId;
} }
@Override
public boolean equals(Object o) {
if (o instanceof MailboxProperties) {
MailboxProperties m = (MailboxProperties) o;
return owner == m.owner &&
onion.equals(m.onion) &&
authToken.equals(m.authToken) &&
NullSafety.equals(inboxId, m.inboxId) &&
NullSafety.equals(outboxId, m.outboxId) &&
serverSupports.equals(m.serverSupports);
}
return false;
}
@Override
public int hashCode() {
return authToken.hashCode();
}
} }

View File

@@ -32,8 +32,8 @@ public interface MailboxSettingsManager {
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException; MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
void recordSuccessfulConnection(Transaction txn, long now, void recordSuccessfulConnection(Transaction txn, long now)
List<MailboxVersion> versions) throws DbException; throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now) void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException; throws DbException;
@@ -46,28 +46,20 @@ public interface MailboxSettingsManager {
interface MailboxHook { interface MailboxHook {
/** /**
* Called when Briar is paired with a mailbox. * Called when Briar is paired with a mailbox
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @param ownOnion Our new mailbox's onion (56 base32 chars)
*/ */
void mailboxPaired(Transaction txn, MailboxProperties p) void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports)
throws DbException; throws DbException;
/** /**
* Called when the mailbox is unpaired. * Called when the mailbox is unpaired
* *
* @param txn A read-write transaction * @param txn A read-write transaction
*/ */
void mailboxUnpaired(Transaction txn) throws DbException; void mailboxUnpaired(Transaction txn) throws DbException;
/**
* Called when we receive our mailbox's server-supported API versions.
* This happens whenever we successfully check the connectivity of
* our mailbox, so this hook may be called frequently.
*
* @param txn A read-write transaction
*/
void serverSupportedVersionsReceived(Transaction txn,
List<MailboxVersion> serverSupports) throws DbException;
} }
} }

View File

@@ -67,13 +67,6 @@ public class MailboxStatus {
return attemptsSinceSuccess; return attemptsSinceSuccess;
} }
/**
* Returns the mailbox's supported API versions.
*/
public List<MailboxVersion> getServerSupports() {
return serverSupports;
}
/** /**
* @return true if this status indicates a problem with the mailbox. * @return true if this status indicates a problem with the mailbox.
*/ */

View File

@@ -29,35 +29,19 @@ public interface MailboxUpdateManager {
/** /**
* The number of properties required for an update message with a mailbox. * The number of properties required for an update message with a mailbox.
* <p>
* The required properties are {@link #PROP_KEY_ONION},
* {@link #PROP_KEY_AUTHTOKEN}, {@link #PROP_KEY_INBOXID} and
* {@link #PROP_KEY_OUTBOXID}.
*/ */
int PROP_COUNT = 4; int PROP_COUNT = 4;
/** /**
* The onion address of the mailbox, excluding the .onion suffix. * The required properties of an update message with a mailbox.
*/ */
String PROP_KEY_ONION = "onion"; String PROP_KEY_ONION = "onion";
/**
* A bearer token for accessing the mailbox (64 hex digits).
*/
String PROP_KEY_AUTHTOKEN = "authToken"; String PROP_KEY_AUTHTOKEN = "authToken";
/**
* A folder ID for downloading messages (64 hex digits).
*/
String PROP_KEY_INBOXID = "inboxId"; String PROP_KEY_INBOXID = "inboxId";
/**
* A folder ID for uploading messages (64 hex digits).
*/
String PROP_KEY_OUTBOXID = "outboxId"; String PROP_KEY_OUTBOXID = "outboxId";
/** /**
* Length of the {@link #PROP_KEY_ONION} property. * Length of the Onion property.
*/ */
int PROP_ONION_LENGTH = 56; int PROP_ONION_LENGTH = 56;
@@ -79,32 +63,9 @@ public interface MailboxUpdateManager {
*/ */
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports"; String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
/**
* Key in the client's local group for storing the serverSupports list that
* was last sent out, if any.
*/
String GROUP_KEY_SENT_SERVER_SUPPORTS = "sentServerSupports";
/**
* Returns the latest {@link MailboxUpdate} sent to the given contact.
* <p>
* If we have our own mailbox then the update will be a
* {@link MailboxUpdateWithMailbox} containing the
* {@link MailboxProperties} the contact should use for communicating with
* our mailbox.
*/
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c) MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException; throws DbException;
/**
* Returns the latest {@link MailboxUpdate} received from the given
* contact, or null if no update has been received.
* <p>
* If the contact has a mailbox then the update will be a
* {@link MailboxUpdateWithMailbox} containing the
* {@link MailboxProperties} we should use for communicating with the
* contact's mailbox.
*/
@Nullable @Nullable
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c) MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException; throws DbException;

View File

@@ -1,36 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Map;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a mailbox is paired.
*/
@Immutable
@NotNullByDefault
public class MailboxPairedEvent extends Event {
private final MailboxProperties properties;
private final Map<ContactId, MailboxUpdateWithMailbox> localUpdates;
public MailboxPairedEvent(MailboxProperties properties,
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
this.properties = properties;
this.localUpdates = localUpdates;
}
public MailboxProperties getProperties() {
return properties;
}
public Map<ContactId, MailboxUpdateWithMailbox> getLocalUpdates() {
return localUpdates;
}
}

View File

@@ -1,28 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Map;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a mailbox is unpaired.
*/
@Immutable
@NotNullByDefault
public class MailboxUnpairedEvent extends Event {
private final Map<ContactId, MailboxUpdate> localUpdates;
public MailboxUnpairedEvent(Map<ContactId, MailboxUpdate> localUpdates) {
this.localUpdates = localUpdates;
}
public Map<ContactId, MailboxUpdate> getLocalUpdates() {
return localUpdates;
}
}

View File

@@ -1,40 +0,0 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the first mailbox update is sent to a
* newly added contact, which happens in the same transaction in which the
* contact is added.
* <p>
* This event is not broadcast when the first mailbox update is sent to an
* existing contact when setting up the
* {@link MailboxUpdateManager mailbox update client}.
*/
@Immutable
@NotNullByDefault
public class MailboxUpdateSentToNewContactEvent extends Event {
private final ContactId contactId;
private final MailboxUpdate mailboxUpdate;
public MailboxUpdateSentToNewContactEvent(ContactId contactId,
MailboxUpdate mailboxUpdate) {
this.contactId = contactId;
this.mailboxUpdate = mailboxUpdate;
}
public ContactId getContactId() {
return contactId;
}
public MailboxUpdate getMailboxUpdate() {
return mailboxUpdate;
}
}

View File

@@ -1,7 +1,9 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.event.EventBus;
public interface BackoffFactory { public interface BackoffFactory {
Backoff createBackoff(int minInterval, int maxInterval, Backoff createBackoff(EventBus eventBus, TransportId transportId,
double base); int minInterval, int maxInterval, double base);
} }

View File

@@ -56,4 +56,9 @@ public interface PluginCallback extends ConnectionHandler {
* This method can safely be called while holding a lock. * This method can safely be called while holding a lock.
*/ */
void pluginStateChanged(State state); void pluginStateChanged(State state);
/**
* Informs the callback that the plugin's polling interval has decreased.
*/
void pollingIntervalDecreased();
} }

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.api.plugin.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 a plugin's polling interval decreases.
*/
@Immutable
@NotNullByDefault
public class PollingIntervalDecreasedEvent extends Event {
private final TransportId transportId;
public PollingIntervalDecreasedEvent(TransportId transportId) {
this.transportId = transportId;
}
public TransportId getTransportId() {
return transportId;
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* An interface for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession} so they can be recorded in the DB as sent
* or acked at some later time.
*/
public interface DeferredSendHandler {
void onAckSent(Collection<MessageId> acked);
void onMessageSent(MessageId sent);
}

View File

@@ -1,37 +0,0 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.ThreadSafe;
/**
* A container for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession}, so they can be recorded in the DB as sent
* or acked at some later time.
*/
@ThreadSafe
@NotNullByDefault
public class OutgoingSessionRecord {
private final Collection<MessageId> ackedIds = new CopyOnWriteArrayList<>();
private final Collection<MessageId> sentIds = new CopyOnWriteArrayList<>();
public void onAckSent(Collection<MessageId> acked) {
ackedIds.addAll(acked);
}
public void onMessageSent(MessageId sent) {
sentIds.add(sent);
}
public Collection<MessageId> getAckedIds() {
return ackedIds;
}
public Collection<MessageId> getSentIds() {
return sentIds;
}
}

View File

@@ -12,30 +12,12 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface SyncSessionFactory { public interface SyncSessionFactory {
/**
* Creates a session for receiving data from a contact.
*/
SyncSession createIncomingSession(ContactId c, InputStream in, SyncSession createIncomingSession(ContactId c, InputStream in,
PriorityHandler handler); PriorityHandler handler);
/**
* Creates a session for sending data to a contact over a simplex transport.
*
* @param eager True if messages should be sent eagerly, ie regardless of
* whether they're due for retransmission.
*/
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, boolean eager, StreamWriter streamWriter); long maxLatency, boolean eager, StreamWriter streamWriter);
/**
* Creates a session for sending data to a contact via a mailbox. The IDs
* of any messages sent or acked will be added to the given
* {@link OutgoingSessionRecord}.
*/
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, StreamWriter streamWriter,
OutgoingSessionRecord sessionRecord);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, int maxIdleTime, StreamWriter streamWriter, long maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority); @Nullable Priority priority);

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group.Visibility;
import java.util.Collection; import java.util.Collection;
@@ -16,19 +15,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class GroupVisibilityUpdatedEvent extends Event { public class GroupVisibilityUpdatedEvent extends Event {
private final Visibility visibility;
private final Collection<ContactId> affected; private final Collection<ContactId> affected;
public GroupVisibilityUpdatedEvent(Visibility visibility, public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
Collection<ContactId> affected) {
this.visibility = visibility;
this.affected = affected; this.affected = affected;
} }
public Visibility getVisibility() {
return visibility;
}
/** /**
* Returns the contacts affected by the update. * Returns the contacts affected by the update.
*/ */

View File

@@ -1,14 +1,9 @@
package org.briarproject.bramble.api.sync.event; package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import java.util.Map;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
@@ -19,32 +14,12 @@ import javax.annotation.concurrent.Immutable;
public class MessageSharedEvent extends Event { public class MessageSharedEvent extends Event {
private final MessageId messageId; private final MessageId messageId;
private final GroupId groupId;
private final Map<ContactId, Boolean> groupVisibility;
public MessageSharedEvent(MessageId message, GroupId groupId, public MessageSharedEvent(MessageId message) {
Map<ContactId, Boolean> groupVisibility) {
this.messageId = message; this.messageId = message;
this.groupId = groupId;
this.groupVisibility = groupVisibility;
} }
public MessageId getMessageId() { public MessageId getMessageId() {
return messageId; return messageId;
} }
public GroupId getGroupId() {
return groupId;
}
/**
* Returns the IDs of all contacts for which the visibility of the
* message's group is either {@link Visibility#SHARED shared} or
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
* group is {@link Visibility#SHARED shared} or false if the group is
* {@link Visibility#VISIBLE visible}.
*/
public Map<ContactId, Boolean> getGroupVisibility() {
return groupVisibility;
}
} }

View File

@@ -40,7 +40,7 @@ public class IoUtils {
} }
} }
public static void delete(File f) { private static void delete(File f) {
if (!f.delete() && LOG.isLoggable(WARNING)) if (!f.delete() && LOG.isLoggable(WARNING))
LOG.warning("Could not delete " + f.getAbsolutePath()); LOG.warning("Could not delete " + f.getAbsolutePath());
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.util; package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -96,10 +95,10 @@ public class StringUtils {
/** /**
* Converts the given hex string to a byte array. * Converts the given hex string to a byte array.
*/ */
public static byte[] fromHexString(String hex) throws FormatException { public static byte[] fromHexString(String hex) {
int len = hex.length(); int len = hex.length();
if (len % 2 != 0) if (len % 2 != 0)
throw new FormatException(); throw new IllegalArgumentException("Not a hex string");
byte[] bytes = new byte[len / 2]; byte[] bytes = new byte[len / 2];
for (int i = 0, j = 0; i < len; i += 2, j++) { for (int i = 0, j = 0; i < len; i += 2, j++) {
int high = hexDigitToInt(hex.charAt(i)); int high = hexDigitToInt(hex.charAt(i));
@@ -109,11 +108,11 @@ public class StringUtils {
return bytes; return bytes;
} }
private static int hexDigitToInt(char c) throws FormatException { private static int hexDigitToInt(char c) {
if (c >= '0' && c <= '9') return c - '0'; if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10;
throw new FormatException(); throw new IllegalArgumentException("Not a hex digit: " + c);
} }
public static String trim(String s) { public static String trim(String s) {
@@ -131,13 +130,13 @@ public class StringUtils {
return MAC.matcher(mac).matches(); return MAC.matcher(mac).matches();
} }
public static byte[] macToBytes(String mac) throws FormatException { public static byte[] macToBytes(String mac) {
if (!MAC.matcher(mac).matches()) throw new FormatException(); if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
return fromHexString(mac.replaceAll(":", "")); return fromHexString(mac.replaceAll(":", ""));
} }
public static String macToString(byte[] mac) throws FormatException { public static String macToString(byte[] mac) {
if (mac.length != 6) throw new FormatException(); if (mac.length != 6) throw new IllegalArgumentException();
StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder();
for (byte b : mac) { for (byte b : mac) {
if (s.length() > 0) s.append(':'); if (s.length() > 0) s.append(':');

View File

@@ -230,14 +230,14 @@ public class TestUtils {
public static MailboxProperties getMailboxProperties(boolean owner, public static MailboxProperties getMailboxProperties(boolean owner,
List<MailboxVersion> serverSupports) { List<MailboxVersion> serverSupports) {
String onion = getRandomString(56); String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId()); MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
if (owner) { if (owner) {
return new MailboxProperties(onion, authToken, serverSupports); return new MailboxProperties(baseUrl, authToken, serverSupports);
} }
MailboxFolderId inboxId = new MailboxFolderId(getRandomId()); MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId()); MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxProperties(onion, authToken, serverSupports, return new MailboxProperties(baseUrl, authToken, serverSupports,
inboxId, outboxId); inboxId, outboxId);
} }
@@ -338,17 +338,6 @@ public class TestUtils {
return false; return false;
} }
public static <E extends Event> E getEvent(Transaction txn,
Class<E> eventClass) {
for (CommitAction action : txn.getActions()) {
if (action instanceof EventAction) {
Event event = ((EventAction) action).getEvent();
if (eventClass.isInstance(event)) return eventClass.cast(event);
}
}
throw new AssertionError();
}
public static boolean isCryptoStrengthUnlimited() { public static boolean isCryptoStrengthUnlimited() {
try { try {
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")

View File

@@ -7,7 +7,6 @@ apply plugin: 'idea'
apply plugin: 'witness' apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
apply from: '../dagger.gradle' apply from: '../dagger.gradle'
apply plugin: 'checkstyle'
dependencies { dependencies {
implementation project(path: ':bramble-api', configuration: 'default') implementation project(path: ':bramble-api', configuration: 'default')
@@ -17,7 +16,7 @@ dependencies {
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.5' implementation 'org.briarproject:jtorctl:0.4'
//noinspection GradleDependency //noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version"

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.account; package org.briarproject.bramble.account;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException; import org.briarproject.bramble.api.crypto.DecryptionException;
@@ -210,13 +209,7 @@ class AccountManagerImpl implements AccountManager {
LOG.warning("Failed to load encrypted database key"); LOG.warning("Failed to load encrypted database key");
throw new DecryptionException(INVALID_CIPHERTEXT); throw new DecryptionException(INVALID_CIPHERTEXT);
} }
byte[] ciphertext; byte[] ciphertext = fromHexString(hex);
try {
ciphertext = fromHexString(hex);
} catch (FormatException e) {
LOG.warning("Encrypted database key has invalid format");
throw new DecryptionException(INVALID_CIPHERTEXT);
}
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener(); KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password, byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
keyStrengthener); keyStrengthener);

View File

@@ -52,7 +52,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.sort;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
@@ -457,7 +456,8 @@ class ClientHelperImpl implements ClientHelper {
checkLength(inboxId, UniqueId.LENGTH); checkLength(inboxId, UniqueId.LENGTH);
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID); byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
checkLength(outboxId, UniqueId.LENGTH); checkLength(outboxId, UniqueId.LENGTH);
MailboxProperties props = new MailboxProperties(onion, String baseUrl = "http://" + onion + ".onion"; // TODO
MailboxProperties props = new MailboxProperties(baseUrl,
new MailboxAuthToken(authToken), serverSupportsList, new MailboxAuthToken(authToken), serverSupportsList,
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId)); new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
return new MailboxUpdateWithMailbox(clientSupportsList, props); return new MailboxUpdateWithMailbox(clientSupportsList, props);
@@ -475,8 +475,6 @@ class ClientHelperImpl implements ClientHelper {
list.add(new MailboxVersion(element.getLong(0).intValue(), list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue())); element.getLong(1).intValue()));
} }
// Sort the list of versions for easier comparison
sort(list);
return list; return list;
} }

View File

@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
@@ -101,16 +100,7 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) { TransportConnectionWriter w) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager, ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory, connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w, null)); syncSessionFactory, transportPropertyManager, c, t, w));
}
@Override
public void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w,
sessionRecord));
} }
@Override @Override

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
@@ -17,8 +16,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException; import java.io.IOException;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -29,8 +26,6 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final TransportConnectionWriter writer; private final TransportConnectionWriter writer;
@Nullable
private final OutgoingSessionRecord sessionRecord;
OutgoingSimplexSyncConnection(KeyManager keyManager, OutgoingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry, ConnectionRegistry connectionRegistry,
@@ -39,15 +34,13 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
SyncSessionFactory syncSessionFactory, SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
ContactId contactId, TransportId transportId, ContactId contactId, TransportId transportId,
TransportConnectionWriter writer, TransportConnectionWriter writer) {
@Nullable OutgoingSessionRecord sessionRecord) {
super(keyManager, connectionRegistry, streamReaderFactory, super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory, streamWriterFactory, syncSessionFactory,
transportPropertyManager); transportPropertyManager);
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.writer = writer; this.writer = writer;
this.sessionRecord = sessionRecord;
} }
@Override @Override
@@ -78,16 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId()); ContactId c = requireNonNull(ctx.getContactId());
if (sessionRecord == null) { // Use eager retransmission if the transport is lossy and cheap
// Use eager retransmission if the transport is lossy and cheap return syncSessionFactory.createSimplexOutgoingSession(c,
return syncSessionFactory.createSimplexOutgoingSession(c, ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
ctx.getTransportId(), w.getMaxLatency(), streamWriter);
w.isLossyAndCheap(), streamWriter);
} else {
return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), streamWriter,
sessionRecord);
}
} }
} }

View File

@@ -163,11 +163,16 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns true if there are any acks to send to the given contact. * Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/> * <p/>
* Read-only. * Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/ */
boolean containsAcksToSend(T txn, ContactId c) throws DbException; boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
@@ -207,18 +212,6 @@ interface Database<T> {
*/ */
boolean containsMessage(T txn, MessageId m) throws DbException; boolean containsMessage(T txn, MessageId m) throws DbException;
/**
* Returns true if there are any messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsMessagesToSend(T txn, ContactId c, long maxLatency,
boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given pending contact. * Returns true if the database contains the given pending contact.
* <p/> * <p/>
@@ -320,13 +313,6 @@ interface Database<T> {
*/ */
Group getGroup(T txn, GroupId g) throws DbException; Group getGroup(T txn, GroupId g) throws DbException;
/**
* Returns the ID of the group containing the given message.
* <p/>
* Read-only.
*/
GroupId getGroupId(T txn, MessageId m) throws DbException;
/** /**
* Returns the metadata for the given group. * Returns the metadata for the given group.
* <p/> * <p/>
@@ -352,11 +338,8 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs of all contacts for which the given group's visibility * Returns the IDs of all contacts to which the given group's visibility is
* is either {@link Visibility#SHARED shared} or * either {@link Visibility VISIBLE} or {@link Visibility SHARED}.
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
* group is {@link Visibility#SHARED shared} or false if the group is
* {@link Visibility#VISIBLE visible}.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -597,16 +580,13 @@ interface Database<T> {
/** /**
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact over a transport with * message is due to be sent to the given contact. The returned value may
* the given latency. * be zero if a message is due to be sent immediately, or Long.MAX_VALUE
* <p> * if no messages are scheduled to be sent.
* The returned value may be zero if a message is due to be sent
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
long getNextSendTime(T txn, ContactId c, long maxLatency) long getNextSendTime(T txn, ContactId c) throws DbException;
throws DbException;
/** /**
* Returns the pending contact with the given ID. * Returns the pending contact with the given ID.

View File

@@ -287,12 +287,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
if (shared) { if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
Map<ContactId, Boolean> visibility =
db.getGroupVisibility(txn, m.getGroupId());
transaction.attach(new MessageSharedEvent(m.getId(),
m.getGroupId(), visibility));
}
} }
db.mergeMessageMetadata(txn, m.getId(), meta); db.mergeMessageMetadata(txn, m.getId(), meta);
} }
@@ -347,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean containsAcksToSend(Transaction transaction, ContactId c) public boolean containsAnythingToSend(Transaction transaction, ContactId c,
throws DbException { long maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
return db.containsAcksToSend(txn, c); return db.containsAnythingToSend(txn, c, maxLatency, eager);
} }
@Override @Override
@@ -378,15 +373,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.containsIdentity(txn, a); return db.containsIdentity(txn, a);
} }
@Override
public boolean containsMessagesToSend(Transaction transaction, ContactId c,
long maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsMessagesToSend(txn, c, maxLatency, eager);
}
@Override @Override
public boolean containsPendingContact(Transaction transaction, public boolean containsPendingContact(Transaction transaction,
PendingContactId p) throws DbException { PendingContactId p) throws DbException {
@@ -555,15 +541,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getGroup(txn, g); return db.getGroup(txn, g);
} }
@Override
public GroupId getGroupId(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
return db.getGroupId(txn, m);
}
@Override @Override
public Metadata getGroupMetadata(Transaction transaction, GroupId g) public Metadata getGroupMetadata(Transaction transaction, GroupId g)
throws DbException { throws DbException {
@@ -634,11 +611,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public Collection<MessageId> getMessagesToAck(Transaction transaction, public Collection<MessageId> getMessagesToAck(Transaction transaction,
ContactId c) throws DbException { ContactId c, int maxMessages) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
return db.getMessagesToAck(txn, c, Integer.MAX_VALUE); return db.getMessagesToAck(txn, c, maxMessages);
} }
@Override @Override
@@ -760,9 +737,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
public Message getMessageToSend(Transaction transaction, ContactId c, public Message getMessageToSend(Transaction transaction, ContactId c,
MessageId m, long maxLatency, boolean markAsSent) MessageId m, long maxLatency, boolean markAsSent)
throws DbException { throws DbException {
if (markAsSent && transaction.isReadOnly()) { if (transaction.isReadOnly()) throw new IllegalArgumentException();
throw new IllegalArgumentException();
}
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
@@ -830,10 +805,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public long getNextSendTime(Transaction transaction, ContactId c, public long getNextSendTime(Transaction transaction, ContactId c)
long maxLatency) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getNextSendTime(txn, c, maxLatency); return db.getNextSendTime(txn, c);
} }
@Override @Override
@@ -1041,8 +1016,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.getGroupVisibility(txn, id).keySet(); db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id); db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g)); transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE, transaction.attach(new GroupVisibilityUpdatedEvent(affected));
affected));
} }
@Override @Override
@@ -1113,7 +1087,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
db.lowerAckFlag(txn, c, acked); List<MessageId> visible = new ArrayList<>(acked.size());
for (MessageId m : acked) {
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
}
db.lowerAckFlag(txn, c, visible);
} }
@Override @Override
@@ -1163,7 +1141,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g); else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED); else db.setGroupVisibility(txn, c, g, v == SHARED);
List<ContactId> affected = singletonList(c); List<ContactId> affected = singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
} }
@Override @Override
@@ -1196,9 +1174,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (db.getMessageState(txn, m) != DELIVERED) if (db.getMessageState(txn, m) != DELIVERED)
throw new IllegalArgumentException("Shared undelivered message"); throw new IllegalArgumentException("Shared undelivered message");
db.setMessageShared(txn, m, true); db.setMessageShared(txn, m, true);
GroupId g = db.getGroupId(txn, m); transaction.attach(new MessageSharedEvent(m));
Map<ContactId, Boolean> visibility = db.getGroupVisibility(txn, g);
transaction.attach(new MessageSharedEvent(m, g, visibility));
} }
@Override @Override

View File

@@ -1147,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public boolean containsAcksToSend(Connection txn, ContactId c) public boolean containsAnythingToSend(Connection txn, ContactId c,
throws DbException { long maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -1160,7 +1160,34 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean acksToSend = rs.next(); boolean acksToSend = rs.next();
rs.close(); rs.close();
ps.close(); ps.close();
return acksToSend; if (acksToSend) return true;
if (eager) {
sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, maxLatency);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -1280,46 +1307,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsMessagesToSend(Connection txn, ContactId c,
long maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
if (eager) {
String sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, maxLatency);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsPendingContact(Connection txn, PendingContactId p) public boolean containsPendingContact(Connection txn, PendingContactId p)
throws DbException { throws DbException {
@@ -1683,27 +1670,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public GroupId getGroupId(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT groupId FROM messages WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
GroupId g = new GroupId(rs.getBytes(1));
rs.close();
ps.close();
return g;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<Group> getGroups(Connection txn, ClientId c, public Collection<Group> getGroups(Connection txn, ClientId c,
int majorVersion) throws DbException { int majorVersion) throws DbException {
@@ -2511,28 +2477,12 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public long getNextSendTime(Connection txn, ContactId c, long maxLatency) public long getNextSendTime(Connection txn, ContactId c)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
// Are any messages sendable immediately? String sql = "SELECT expiry FROM statuses"
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (maxLatency IS NULL OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, maxLatency);
rs = ps.executeQuery();
boolean found = rs.next();
rs.close();
ps.close();
if (found) return 0;
// When is the earliest expiry time (could be in the past)?
sql = "SELECT expiry FROM statuses"
+ " WHERE contactId = ? AND state = ?" + " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE" + " AND deleted = FALSE AND seen = FALSE"

View File

@@ -18,7 +18,7 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.Semaphore;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -29,12 +29,10 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.CREATED;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
@@ -62,11 +60,12 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final List<Service> services; private final List<Service> services;
private final List<OpenDatabaseHook> openDatabaseHooks; private final List<OpenDatabaseHook> openDatabaseHooks;
private final List<ExecutorService> executors; private final List<ExecutorService> executors;
private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1); private final CountDownLatch dbLatch = new CountDownLatch(1);
private final CountDownLatch startupLatch = new CountDownLatch(1); private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1); private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final AtomicReference<LifecycleState> state =
new AtomicReference<>(CREATED); private volatile LifecycleState state = STARTING;
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
@@ -103,8 +102,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Override @Override
public StartResult startServices(SecretKey dbKey) { public StartResult startServices(SecretKey dbKey) {
if (!state.compareAndSet(CREATED, STARTING)) { if (!startStopSemaphore.tryAcquire()) {
LOG.warning("Already running"); LOG.info("Already starting or stopping");
return ALREADY_RUNNING; return ALREADY_RUNNING;
} }
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
@@ -136,7 +135,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}); });
LOG.info("Starting services"); LOG.info("Starting services");
state.set(STARTING_SERVICES); state = STARTING_SERVICES;
dbLatch.countDown(); dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES)); eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
@@ -149,7 +148,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} }
} }
state.set(RUNNING); state = RUNNING;
startupLatch.countDown(); startupLatch.countDown();
eventBus.broadcast(new LifecycleEvent(RUNNING)); eventBus.broadcast(new LifecycleEvent(RUNNING));
return SUCCESS; return SUCCESS;
@@ -165,58 +164,63 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} catch (ServiceException e) { } catch (ServiceException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return SERVICE_ERROR; return SERVICE_ERROR;
} finally {
startStopSemaphore.release();
} }
} }
@Override @Override
public void onDatabaseMigration() { public void onDatabaseMigration() {
state.set(MIGRATING_DATABASE); state = MIGRATING_DATABASE;
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE)); eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
} }
@Override @Override
public void onDatabaseCompaction() { public void onDatabaseCompaction() {
state.set(COMPACTING_DATABASE); state = COMPACTING_DATABASE;
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE)); eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
} }
@Override @Override
public void stopServices() { public void stopServices() {
if (!state.compareAndSet(RUNNING, STOPPING)) { try {
LOG.warning("Not running"); startStopSemaphore.acquire();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting to stop services");
return; return;
} }
LOG.info("Stopping services"); try {
eventBus.broadcast(new LifecycleEvent(STOPPING)); if (state == STOPPING) {
for (Service s : services) { LOG.info("Already stopped");
try { return;
}
LOG.info("Stopping services");
state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING));
for (Service s : services) {
long start = now(); long start = now();
s.stopService(); s.stopService();
if (LOG.isLoggable(FINE)) { if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Stopping service " logDuration(LOG, "Stopping service "
+ s.getClass().getSimpleName(), start); + s.getClass().getSimpleName(), start);
} }
} catch (ServiceException e) {
logException(LOG, WARNING, e);
} }
} for (ExecutorService e : executors) {
for (ExecutorService e : executors) { if (LOG.isLoggable(FINE)) {
if (LOG.isLoggable(FINE)) { LOG.fine("Stopping executor "
LOG.fine("Stopping executor " + e.getClass().getSimpleName());
+ e.getClass().getSimpleName()); }
e.shutdownNow();
} }
e.shutdownNow();
}
try {
long start = now(); long start = now();
db.close(); db.close();
logDuration(LOG, "Closing database", start); logDuration(LOG, "Closing database", start);
} catch (DbException e) { shutdownLatch.countDown();
} catch (DbException | ServiceException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} finally {
startStopSemaphore.release();
} }
state.set(STOPPED);
shutdownLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STOPPED));
} }
@Override @Override
@@ -236,6 +240,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Override @Override
public LifecycleState getLifecycleState() { public LifecycleState getLifecycleState() {
return state.get(); return state;
} }
} }

View File

@@ -1,124 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
class ContactMailboxClient implements MailboxClient {
private static final Logger LOG =
getLogger(ContactMailboxClient.class.getName());
private final MailboxWorkerFactory workerFactory;
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor reachabilityMonitor;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private MailboxWorker uploadWorker = null, downloadWorker = null;
ContactMailboxClient(MailboxWorkerFactory workerFactory,
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor) {
this.workerFactory = workerFactory;
this.connectivityChecker = connectivityChecker;
this.reachabilityMonitor = reachabilityMonitor;
}
@Override
public void start() {
LOG.info("Started");
// Nothing to do until contact is assigned
}
@Override
public void destroy() {
LOG.info("Destroyed");
MailboxWorker uploadWorker, downloadWorker;
synchronized (lock) {
uploadWorker = this.uploadWorker;
this.uploadWorker = null;
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
}
if (uploadWorker != null) uploadWorker.destroy();
if (downloadWorker != null) downloadWorker.destroy();
// The connectivity checker belongs to the client, so it should be
// destroyed. The Tor reachability monitor is shared between clients,
// so it should not be destroyed
connectivityChecker.destroy();
}
@Override
public void assignContactForUpload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for upload");
if (properties.isOwner()) throw new IllegalArgumentException();
// For a contact's mailbox we should always be uploading to the outbox
// assigned to us by the contact
if (!folderId.equals(properties.getOutboxId())) {
throw new IllegalArgumentException();
}
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
connectivityChecker, properties, folderId, contactId);
synchronized (lock) {
if (this.uploadWorker != null) throw new IllegalStateException();
this.uploadWorker = uploadWorker;
}
uploadWorker.start();
}
@Override
public void deassignContactForUpload(ContactId contactId) {
LOG.info("Contact deassigned for upload");
MailboxWorker uploadWorker;
synchronized (lock) {
uploadWorker = this.uploadWorker;
this.uploadWorker = null;
}
if (uploadWorker != null) uploadWorker.destroy();
}
@Override
public void assignContactForDownload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for download");
if (properties.isOwner()) throw new IllegalArgumentException();
// For a contact's mailbox we should always be downloading from the
// inbox assigned to us by the contact
if (!folderId.equals(properties.getInboxId())) {
throw new IllegalArgumentException();
}
MailboxWorker downloadWorker =
workerFactory.createDownloadWorkerForContactMailbox(
connectivityChecker, reachabilityMonitor, properties);
synchronized (lock) {
if (this.downloadWorker != null) throw new IllegalStateException();
this.downloadWorker = downloadWorker;
}
downloadWorker.start();
}
@Override
public void deassignContactForDownload(ContactId contactId) {
LOG.info("Contact deassigned for download");
MailboxWorker downloadWorker;
synchronized (lock) {
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
}
if (downloadWorker != null) downloadWorker.destroy();
}
}

View File

@@ -5,23 +5,14 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl { class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private static final Logger LOG =
getLogger(ContactMailboxConnectivityChecker.class.getName());
private final MailboxApi mailboxApi; private final MailboxApi mailboxApi;
@Inject
ContactMailboxConnectivityChecker(Clock clock, ContactMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) { MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
super(clock, mailboxApiCaller); super(clock, mailboxApiCaller);
@@ -32,9 +23,7 @@ class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
ApiCall createConnectivityCheckTask(MailboxProperties properties) { ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (properties.isOwner()) throw new IllegalArgumentException(); if (properties.isOwner()) throw new IllegalArgumentException();
return new SimpleApiCall(() -> { return new SimpleApiCall(() -> {
LOG.info("Checking connectivity of contact's mailbox");
if (!mailboxApi.checkStatus(properties)) throw new ApiException(); if (!mailboxApi.checkStatus(properties)) throw new ApiException();
LOG.info("Contact's mailbox is reachable");
// Call the observers and cache the result // Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis()); onConnectivityCheckSucceeded(clock.currentTimeMillis());
}); });

View File

@@ -1,25 +1,73 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxFolderId; import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile; import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException; import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.util.Collections.emptyList; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class ContactMailboxDownloadWorker extends MailboxDownloadWorker { class ContactMailboxDownloadWorker implements MailboxWorker,
ConnectivityObserver, TorReachabilityObserver {
/**
* When the worker is started it waits for a connectivity check, then
* starts its first download cycle: checking the inbox, downloading and
* deleting any files, and checking again until the inbox is empty.
* <p>
* The worker then waits for our Tor hidden service to be reachable before
* starting its second download cycle. This ensures that if a contact
* tried and failed to connect to our hidden service before it was
* reachable, and therefore uploaded a file to the mailbox instead, we'll
* find the file in the second download cycle.
*/
private enum State {
CREATED,
CONNECTIVITY_CHECK,
DOWNLOAD_CYCLE_1,
WAITING_FOR_TOR,
DOWNLOAD_CYCLE_2,
FINISHED,
DESTROYED
}
private static final Logger LOG =
getLogger(ContactMailboxDownloadWorker.class.getName());
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor torReachabilityMonitor;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxProperties mailboxProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable apiCall = null;
ContactMailboxDownloadWorker( ContactMailboxDownloadWorker(
ConnectivityChecker connectivityChecker, ConnectivityChecker connectivityChecker,
@@ -28,14 +76,57 @@ class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
MailboxApi mailboxApi, MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager, MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) { MailboxProperties mailboxProperties) {
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
mailboxApi, mailboxFileManager, mailboxProperties);
if (mailboxProperties.isOwner()) throw new IllegalArgumentException(); if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
this.connectivityChecker = connectivityChecker;
this.torReachabilityMonitor = torReachabilityMonitor;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
} }
@Override @Override
protected ApiCall createApiCallForDownloadCycle() { public void start() {
return new SimpleApiCall(this::apiCallListInbox); LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) throw new IllegalStateException();
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
torReachabilityMonitor.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.DOWNLOAD_CYCLE_1;
// Start first download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
} }
private void apiCallListInbox() throws IOException, ApiException { private void apiCallListInbox() throws IOException, ApiException {
@@ -43,23 +134,108 @@ class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
if (state == State.DESTROYED) return; if (state == State.DESTROYED) return;
} }
LOG.info("Listing inbox"); LOG.info("Listing inbox");
MailboxFolderId folderId = List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()); requireNonNull(mailboxProperties.getInboxId()));
List<MailboxFile> files; if (files.isEmpty()) onDownloadCycleFinished();
try { else downloadNextFile(new LinkedList<>(files));
files = mailboxApi.getFiles(mailboxProperties, folderId); }
} catch (TolerableFailureException e) {
LOG.warning("Inbox folder does not exist"); private void onDownloadCycleFinished() {
files = emptyList(); boolean addObserver = false;
} synchronized (lock) {
if (files.isEmpty()) { if (state == State.DOWNLOAD_CYCLE_1) {
onDownloadCycleFinished(); LOG.info("First download cycle finished");
} else { state = State.WAITING_FOR_TOR;
Queue<FolderFile> queue = new LinkedList<>(); addObserver = true;
for (MailboxFile file : files) { } else if (state == State.DOWNLOAD_CYCLE_2) {
queue.add(new FolderFile(folderId, file.name)); LOG.info("Second download cycle finished");
state = State.FINISHED;
} }
}
if (addObserver) {
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
torReachabilityMonitor.addOneShotObserver(this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) torReachabilityMonitor.removeObserver(this);
}
}
private void downloadNextFile(Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
MailboxFile file = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDownloadFile(file, queue)));
}
}
private void apiCallDownloadFile(MailboxFile file,
Queue<MailboxFile> queue) throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Downloading file");
File tempFile = mailboxFileManager.createTempFileForDownload();
try {
mailboxApi.getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file.name, tempFile);
} catch (IOException | ApiException e) {
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
throw e;
}
mailboxFileManager.handleDownloadedFile(tempFile);
deleteFile(file, queue);
}
private void deleteFile(MailboxFile file, Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
}
}
private void apiCallDeleteFile(MailboxFile file, Queue<MailboxFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
try {
mailboxApi.deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file.name);
} catch (TolerableFailureException e) {
// Catch this so we can continue to the next file
logException(LOG, INFO, e);
}
if (queue.isEmpty()) {
// List the inbox again to check for files that may have arrived
// while we were downloading
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
} else {
downloadNextFile(queue); downloadNextFile(queue);
} }
} }
@Override
public void onTorReachable() {
LOG.info("Our Tor hidden service is reachable");
synchronized (lock) {
if (state != State.WAITING_FOR_TOR) return;
state = State.DOWNLOAD_CYCLE_2;
// Start second download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
}
} }

View File

@@ -91,13 +91,9 @@ interface MailboxApi {
* Used by owner and contacts to list their files to retrieve. * Used by owner and contacts to list their files to retrieve.
* <p> * <p>
* Returns 200 OK with the list of files in JSON. * Returns 200 OK with the list of files in JSON.
*
* @throws TolerableFailureException if response code is 404 (folder does
* not exist or client is not authorised to download from it)
*/ */
List<MailboxFile> getFiles(MailboxProperties properties, List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) MailboxFolderId folderId) throws IOException, ApiException;
throws IOException, ApiException, TolerableFailureException;
/** /**
* Used by owner and contacts to retrieve a file. * Used by owner and contacts to retrieve a file.
@@ -106,22 +102,17 @@ interface MailboxApi {
* in the response body. * in the response body.
* *
* @param file the empty file the response bytes will be written into. * @param file the empty file the response bytes will be written into.
* @throws TolerableFailureException if response code is 404 (folder does
* not exist, client is not authorised to download from folder, or file
* does not exist)
*/ */
void getFile(MailboxProperties properties, MailboxFolderId folderId, void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) MailboxFileId fileId, File file) throws IOException, ApiException;
throws IOException, ApiException, TolerableFailureException;
/** /**
* Used by owner and contacts to delete files. * Used by owner and contacts to delete files.
* <p> * <p>
* Returns 200 OK (no exception) if deletion was successful. * Returns 200 OK (no exception) if deletion was successful.
* *
* @throws TolerableFailureException if response code is 404 (folder does * @throws TolerableFailureException on 404 response,
* not exist, client is not authorised to download from folder, or file * because file was most likely deleted already.
* does not exist)
*/ */
void deleteFile(MailboxProperties properties, MailboxFolderId folderId, void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId) MailboxFileId fileId)

View File

@@ -22,6 +22,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@@ -34,7 +35,6 @@ import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES; import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static okhttp3.internal.Util.EMPTY_REQUEST; import static okhttp3.internal.Util.EMPTY_REQUEST;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@@ -42,22 +42,18 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@NotNullByDefault @NotNullByDefault
class MailboxApiImpl implements MailboxApi { class MailboxApiImpl implements MailboxApi {
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
private final JsonMapper mapper = JsonMapper.builder()
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.build();
private static final MediaType JSON = private static final MediaType JSON =
requireNonNull(MediaType.parse("application/json; charset=utf-8")); requireNonNull(MediaType.parse("application/json; charset=utf-8"));
private static final MediaType FILE = private static final MediaType FILE =
requireNonNull(MediaType.parse("application/octet-stream")); requireNonNull(MediaType.parse("application/octet-stream"));
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
private final JsonMapper mapper = JsonMapper.builder()
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.build();
private final UrlConverter urlConverter;
@Inject @Inject
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider, MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
UrlConverter urlConverter) {
this.httpClientProvider = httpClientProvider; this.httpClientProvider = httpClientProvider;
this.urlConverter = urlConverter;
} }
@Override @Override
@@ -82,7 +78,7 @@ class MailboxApiImpl implements MailboxApi {
throws IOException, ApiException { throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException(); if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + "/setup") .url(properties.getBaseUrl() + "/setup")
.put(EMPTY_REQUEST) .put(EMPTY_REQUEST)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
@@ -97,7 +93,7 @@ class MailboxApiImpl implements MailboxApi {
if (tokenNode == null) { if (tokenNode == null) {
throw new ApiException(); throw new ApiException();
} }
return new MailboxProperties(properties.getOnion(), return new MailboxProperties(properties.getBaseUrl(),
MailboxAuthToken.fromString(tokenNode.textValue()), MailboxAuthToken.fromString(tokenNode.textValue()),
parseServerSupports(node)); parseServerSupports(node));
} catch (JacksonException | InvalidMailboxIdException e) { } catch (JacksonException | InvalidMailboxIdException e) {
@@ -125,8 +121,6 @@ class MailboxApiImpl implements MailboxApi {
if (major < 0 || minor < 0) throw new ApiException(); if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor)); serverSupports.add(new MailboxVersion(major, minor));
} }
// Sort the list of versions for easier comparison
sort(serverSupports);
return serverSupports; return serverSupports;
} }
@@ -143,7 +137,7 @@ class MailboxApiImpl implements MailboxApi {
throws IOException, ApiException { throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException(); if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + "/") .url(properties.getBaseUrl() + "/")
.delete() .delete()
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
@@ -168,7 +162,7 @@ class MailboxApiImpl implements MailboxApi {
public void deleteContact(MailboxProperties properties, ContactId contactId) public void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException { throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException(); if (!properties.isOwner()) throw new IllegalArgumentException();
String url = getBaseUrl(properties) + "/contacts/" + String url = properties.getBaseUrl() + "/contacts/" +
contactId.getInt(); contactId.getInt();
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.delete() .delete()
@@ -218,11 +212,9 @@ class MailboxApiImpl implements MailboxApi {
@Override @Override
public List<MailboxFile> getFiles(MailboxProperties properties, public List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) MailboxFolderId folderId) throws IOException, ApiException {
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId; String path = "/files/" + folderId;
Response response = sendGetRequest(properties, path); Response response = sendGetRequest(properties, path);
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException(); if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body(); ResponseBody body = response.body();
@@ -247,7 +239,7 @@ class MailboxApiImpl implements MailboxApi {
if (time < 1) throw new ApiException(); if (time < 1) throw new ApiException();
list.add(new MailboxFile(MailboxFileId.fromString(name), time)); list.add(new MailboxFile(MailboxFileId.fromString(name), time));
} }
sort(list); Collections.sort(list);
return list; return list;
} catch (JacksonException | InvalidMailboxIdException e) { } catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException(); throw new ApiException();
@@ -256,11 +248,9 @@ class MailboxApiImpl implements MailboxApi {
@Override @Override
public void getFile(MailboxProperties properties, MailboxFolderId folderId, public void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) MailboxFileId fileId, File file) throws IOException, ApiException {
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId + "/" + fileId; String path = "/files/" + folderId + "/" + fileId;
Response response = sendGetRequest(properties, path); Response response = sendGetRequest(properties, path);
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException(); if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body(); ResponseBody body = response.body();
@@ -276,7 +266,7 @@ class MailboxApiImpl implements MailboxApi {
String path = "/files/" + folderId + "/" + fileId; String path = "/files/" + folderId + "/" + fileId;
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.delete() .delete()
.url(getBaseUrl(properties) + path) .url(properties.getBaseUrl() + path)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
@@ -318,7 +308,7 @@ class MailboxApiImpl implements MailboxApi {
private Response sendGetRequest(MailboxProperties properties, String path) private Response sendGetRequest(MailboxProperties properties, String path)
throws IOException { throws IOException {
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + path) .url(properties.getBaseUrl() + path)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute(); return client.newCall(request).execute();
@@ -327,7 +317,7 @@ class MailboxApiImpl implements MailboxApi {
private Response sendPostRequest(MailboxProperties properties, String path, private Response sendPostRequest(MailboxProperties properties, String path,
RequestBody body) throws IOException { RequestBody body) throws IOException {
Request request = getRequestBuilder(properties.getAuthToken()) Request request = getRequestBuilder(properties.getAuthToken())
.url(getBaseUrl(properties) + path) .url(properties.getBaseUrl() + path)
.post(body) .post(body)
.build(); .build();
OkHttpClient client = httpClientProvider.get(); OkHttpClient client = httpClientProvider.get();
@@ -349,7 +339,4 @@ class MailboxApiImpl implements MailboxApi {
return (ArrayNode) arrayNode; return (ArrayNode) arrayNode;
} }
private String getBaseUrl(MailboxProperties properties) {
return urlConverter.convertOnionToBaseUrl(properties.getOnion());
}
} }

View File

@@ -1,55 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
interface MailboxClient {
/**
* Asynchronously starts the client.
*/
void start();
/**
* Destroys the client and its workers, cancelling any pending tasks or
* retries.
*/
void destroy();
/**
* Assigns a contact to the client for upload.
*
* @param properties Properties for communicating with the mailbox
* managed by this client.
* @param folderId The ID of the folder to which files will be uploaded.
*/
void assignContactForUpload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for upload.
*/
void deassignContactForUpload(ContactId c);
/**
* Assigns a contact to the client for download.
*
* @param properties Properties for communicating with the mailbox
* managed by this client.
* @param folderId The ID of the folder from which files will be
* downloaded.
*/
void assignContactForDownload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for download.
*/
void deassignContactForDownload(ContactId c);
}

View File

@@ -1,21 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface MailboxClientFactory {
/**
* Creates a client for communicating with a contact's mailbox.
*/
MailboxClient createContactMailboxClient(
TorReachabilityMonitor reachabilityMonitor);
/**
* Creates a client for communicating with our own mailbox.
*/
MailboxClient createOwnMailboxClient(
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
}

View File

@@ -1,52 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Provider;
@NotNullByDefault
class MailboxClientFactoryImpl implements MailboxClientFactory {
private final MailboxWorkerFactory workerFactory;
private final TaskScheduler taskScheduler;
private final Executor ioExecutor;
private final Provider<ContactMailboxConnectivityChecker>
contactCheckerProvider;
private final Provider<OwnMailboxConnectivityChecker> ownCheckerProvider;
@Inject
MailboxClientFactoryImpl(MailboxWorkerFactory workerFactory,
TaskScheduler taskScheduler,
@IoExecutor Executor ioExecutor,
Provider<ContactMailboxConnectivityChecker> contactCheckerProvider,
Provider<OwnMailboxConnectivityChecker> ownCheckerProvider) {
this.workerFactory = workerFactory;
this.taskScheduler = taskScheduler;
this.ioExecutor = ioExecutor;
this.contactCheckerProvider = contactCheckerProvider;
this.ownCheckerProvider = ownCheckerProvider;
}
@Override
public MailboxClient createContactMailboxClient(
TorReachabilityMonitor reachabilityMonitor) {
ConnectivityChecker connectivityChecker = contactCheckerProvider.get();
return new ContactMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor);
}
@Override
public MailboxClient createOwnMailboxClient(
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
ConnectivityChecker connectivityChecker = ownCheckerProvider.get();
return new OwnMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor, taskScheduler, ioExecutor, properties);
}
}

View File

@@ -1,667 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxHelper.isClientCompatibleWithServer;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
/**
* This component manages a {@link MailboxClient} for each mailbox we know
* about and are able to use (our own mailbox and/or contacts' mailboxes).
* The clients are created when we come online (i.e. when the Tor plugin
* becomes {@link Plugin.State#ACTIVE active}) and destroyed when we go
* offline.
* <p/>
* The manager keeps track of the latest {@link MailboxUpdate} sent to and
* received from each contact. These updates are used to decide which
* mailboxes the manager needs clients for, and which mailbox (if any) should
* be used for uploading data to and/or downloading data from each contact.
* <p/>
* The assignments of contacts to mailboxes for upload and/or download may
* change when the following events happen:
* <ul>
* <li> A mailbox is paired or unpaired </li>
* <li> A contact is added or removed </li>
* <li> A {@link MailboxUpdate} is received from a contact </li>
* <li> We discover that our own mailbox's supported API versions have
* changed </li>
* </ul>
* <p/>
* The manager keeps its mutable state consistent with the state in the DB by
* using commit actions and events to update the manager's state on the event
* thread.
*/
@ThreadSafe
@NotNullByDefault
class MailboxClientManager implements Service, EventListener {
private static final Logger LOG =
getLogger(MailboxClientManager.class.getName());
private final Executor eventExecutor, dbExecutor;
private final TransactionManager db;
private final ContactManager contactManager;
private final PluginManager pluginManager;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final MailboxClientFactory mailboxClientFactory;
private final TorReachabilityMonitor reachabilityMonitor;
private final AtomicBoolean used = new AtomicBoolean(false);
// All of the following mutable state must only be accessed on the
// event thread
private final Map<ContactId, Updates> contactUpdates = new HashMap<>();
private final Map<ContactId, MailboxClient> contactClients =
new HashMap<>();
@Nullable
private MailboxProperties ownProperties = null;
@Nullable
private MailboxClient ownClient = null;
private boolean online = false, handleEvents = false;
@Inject
MailboxClientManager(@EventExecutor Executor eventExecutor,
@DatabaseExecutor Executor dbExecutor,
TransactionManager db,
ContactManager contactManager,
PluginManager pluginManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager,
MailboxClientFactory mailboxClientFactory,
TorReachabilityMonitor reachabilityMonitor) {
this.eventExecutor = eventExecutor;
this.dbExecutor = dbExecutor;
this.db = db;
this.contactManager = contactManager;
this.pluginManager = pluginManager;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
this.mailboxClientFactory = mailboxClientFactory;
this.reachabilityMonitor = reachabilityMonitor;
}
@Override
public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException();
reachabilityMonitor.start();
dbExecutor.execute(this::loadMailboxProperties);
}
@DatabaseExecutor
private void loadMailboxProperties() {
LOG.info("Loading mailbox properties");
try {
db.transaction(true, txn -> {
Map<ContactId, Updates> updates = new HashMap<>();
for (Contact c : contactManager.getContacts(txn)) {
MailboxUpdate local = mailboxUpdateManager
.getLocalUpdate(txn, c.getId());
MailboxUpdate remote = mailboxUpdateManager
.getRemoteUpdate(txn, c.getId());
updates.put(c.getId(), new Updates(local, remote));
}
MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn);
// Use a commit action so the state in memory remains
// consistent with the state in the DB
txn.attach(() -> initialiseState(updates, ownProps));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void initialiseState(Map<ContactId, Updates> updates,
@Nullable MailboxProperties ownProps) {
contactUpdates.putAll(updates);
ownProperties = ownProps;
Plugin tor = pluginManager.getPlugin(ID);
if (tor != null && tor.getState() == ACTIVE) {
LOG.info("Online");
online = true;
createClients();
}
// Now that the mutable state has been initialised we can start
// handling events. This is done in a commit action so that we don't
// miss any changes to the DB state or handle events for any changes
// that were already reflected in the initial load
handleEvents = true;
}
@EventExecutor
private void createClients() {
LOG.info("Creating clients");
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (isContactMailboxUsable(u.remote)) {
// Create and start a client for the contact's mailbox
MailboxClient contactClient = createAndStartClient(c);
// Assign the contact to the contact's mailbox for upload
assignContactToContactMailboxForUpload(c, contactClient, u);
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
// We don't have a usable mailbox, so assign the contact to
// the contact's mailbox for download too
assignContactToContactMailboxForDownload(c,
contactClient, u);
}
}
}
if (ownProperties == null) return;
if (!isOwnMailboxUsable(ownProperties)) {
LOG.warning("We have a mailbox but we can't use it");
return;
}
// Create and start a client for our mailbox
createAndStartClientForOwnMailbox();
// Assign contacts to our mailbox for upload/download
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (isOwnMailboxUsable(ownProperties, u.remote)) {
// Assign the contact to our mailbox for download
assignContactToOwnMailboxForDownload(c, u);
if (!isContactMailboxUsable(u.remote)) {
// The contact doesn't have a usable mailbox, so assign
// the contact to our mailbox for upload too
assignContactToOwnMailboxForUpload(c, u);
}
}
}
}
@Override
public void stopService() throws ServiceException {
CountDownLatch latch = new CountDownLatch(1);
eventExecutor.execute(() -> {
handleEvents = false;
if (online) destroyClients();
latch.countDown();
});
reachabilityMonitor.destroy();
try {
latch.await();
} catch (InterruptedException e) {
throw new ServiceException(e);
}
}
@EventExecutor
private void destroyClients() {
LOG.info("Destroying clients");
for (MailboxClient client : contactClients.values()) {
client.destroy();
}
contactClients.clear();
destroyOwnClient();
}
@EventExecutor
private void destroyOwnClient() {
if (ownClient != null) {
ownClient.destroy();
ownClient = null;
}
}
@Override
public void eventOccurred(Event e) {
if (!handleEvents) return;
if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(ID)) onTorActive();
} else if (e instanceof TransportInactiveEvent) {
TransportInactiveEvent t = (TransportInactiveEvent) e;
if (t.getTransportId().equals(ID)) onTorInactive();
} else if (e instanceof MailboxPairedEvent) {
LOG.info("Mailbox paired");
MailboxPairedEvent m = (MailboxPairedEvent) e;
onMailboxPaired(m.getProperties(), m.getLocalUpdates());
} else if (e instanceof MailboxUnpairedEvent) {
LOG.info("Mailbox unpaired");
MailboxUnpairedEvent m = (MailboxUnpairedEvent) e;
onMailboxUnpaired(m.getLocalUpdates());
} else if (e instanceof MailboxUpdateSentToNewContactEvent) {
LOG.info("Contact added");
MailboxUpdateSentToNewContactEvent
m = (MailboxUpdateSentToNewContactEvent) e;
onContactAdded(m.getContactId(), m.getMailboxUpdate());
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed");
ContactRemovedEvent c = (ContactRemovedEvent) e;
onContactRemoved(c.getContactId());
} else if (e instanceof RemoteMailboxUpdateEvent) {
LOG.info("Remote mailbox update");
RemoteMailboxUpdateEvent r = (RemoteMailboxUpdateEvent) e;
onRemoteMailboxUpdate(r.getContact(), r.getMailboxUpdate());
} else if (e instanceof OwnMailboxConnectionStatusEvent) {
OwnMailboxConnectionStatusEvent o =
(OwnMailboxConnectionStatusEvent) e;
onOwnMailboxConnectionStatusChanged(o.getStatus());
}
}
@EventExecutor
private void onTorActive() {
// If we checked the plugin at startup concurrently with the plugin
// becoming active then `online` may already be true when we receive
// the first TransportActiveEvent, in which case ignore it
if (online) return;
LOG.info("Online");
online = true;
createClients();
}
@EventExecutor
private void onTorInactive() {
// If we checked the plugin at startup concurrently with the plugin
// becoming inactive then `online` may already be false when we
// receive the first TransportInactiveEvent, in which case ignore it
if (!online) return;
LOG.info("Offline");
online = false;
destroyClients();
}
@EventExecutor
private void onMailboxPaired(MailboxProperties ownProps,
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
for (Entry<ContactId, MailboxUpdateWithMailbox> e :
localUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = contactUpdates.get(c);
contactUpdates.put(c, new Updates(e.getValue(), u.remote));
}
ownProperties = ownProps;
if (!online) return;
if (!isOwnMailboxUsable(ownProperties)) {
LOG.warning("We have a mailbox but we can't use it");
return;
}
// Create and start a client for our mailbox
createAndStartClientForOwnMailbox();
// Assign contacts to our mailbox for upload/download
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
// Our mailbox isn't usable for communicating with this
// contact, so don't assign/reassign this contact
continue;
}
if (isContactMailboxUsable(u.remote)) {
// The contact has a usable mailbox, so the contact should
// currently be assigned to the contact's mailbox for upload
// and download. Reassign the contact to our mailbox for
// download
MailboxClient contactClient =
requireNonNull(contactClients.get(c));
contactClient.deassignContactForDownload(c);
} else {
// The contact doesn't have a usable mailbox, so assign the
// contact to our mailbox for upload
assignContactToOwnMailboxForUpload(c, u);
}
assignContactToOwnMailboxForDownload(c, u);
}
}
@EventExecutor
private void onMailboxUnpaired(Map<ContactId, MailboxUpdate> localUpdates) {
for (Entry<ContactId, MailboxUpdate> e : localUpdates.entrySet()) {
ContactId c = e.getKey();
Updates updates = contactUpdates.get(c);
contactUpdates.put(c, new Updates(e.getValue(), updates.remote));
}
MailboxProperties oldOwnProperties = ownProperties;
ownProperties = null;
if (!online) return;
// Destroy the client for our own mailbox, if any
destroyOwnClient();
// Reassign contacts to their own mailboxes for download where possible
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
ContactId c = e.getKey();
Updates u = e.getValue();
if (isContactMailboxUsable(u.remote) &&
isOwnMailboxUsable(oldOwnProperties, u.remote)) {
// The contact should currently be assigned to our mailbox
// for download. Reassign the contact to the contact's
// mailbox for download
MailboxClient contactClient =
requireNonNull(contactClients.get(c));
assignContactToContactMailboxForDownload(c, contactClient, u);
}
}
}
@EventExecutor
private void onContactAdded(ContactId c, MailboxUpdate u) {
Updates old = contactUpdates.put(c, new Updates(u, null));
if (old != null) throw new IllegalStateException();
// We haven't yet received an update from the newly added contact,
// so at this stage we don't need to assign the contact to any
// mailboxes for upload or download
}
@EventExecutor
private void onContactRemoved(ContactId c) {
Updates updates = requireNonNull(contactUpdates.remove(c));
if (!online) return;
// Destroy the client for the contact's mailbox, if any
MailboxClient client = contactClients.remove(c);
if (client != null) client.destroy();
// If we have a mailbox and the contact is assigned to it for upload
// and/or download, deassign the contact
if (ownProperties == null) return;
if (isOwnMailboxUsable(ownProperties, updates.remote)) {
// We have a usable mailbox, so the contact should currently be
// assigned to our mailbox for download. Deassign the contact from
// our mailbox for download
requireNonNull(ownClient).deassignContactForDownload(c);
if (!isContactMailboxUsable(updates.remote)) {
// The contact doesn't have a usable mailbox, so the contact
// should currently be assigned to our mailbox for upload.
// Deassign the contact from our mailbox for upload
requireNonNull(ownClient).deassignContactForUpload(c);
}
}
}
@EventExecutor
private void onRemoteMailboxUpdate(ContactId c, MailboxUpdate remote) {
Updates old = contactUpdates.get(c);
MailboxUpdate oldRemote = old.remote;
Updates u = new Updates(old.local, remote);
contactUpdates.put(c, u);
if (!online) return;
// What may have changed?
// * Contact's mailbox may be usable now, was unusable before
// * Contact's mailbox may be unusable now, was usable before
// * Contact's mailbox may have been replaced
// * Contact's mailbox may have changed its API versions
// * Contact may be able to use our mailbox now, was unable before
// * Contact may be unable to use our mailbox now, was able before
boolean wasContactMailboxUsable = isContactMailboxUsable(oldRemote);
boolean isContactMailboxUsable = isContactMailboxUsable(remote);
boolean wasOwnMailboxUsable =
isOwnMailboxUsable(ownProperties, oldRemote);
boolean isOwnMailboxUsable = isOwnMailboxUsable(ownProperties, remote);
// Create/destroy/replace the client for the contact's mailbox if needed
MailboxClient contactClient = null;
boolean clientReplaced = false;
if (isContactMailboxUsable) {
if (wasContactMailboxUsable) {
MailboxProperties oldProps = getMailboxProperties(oldRemote);
MailboxProperties newProps = getMailboxProperties(remote);
if (oldProps.equals(newProps)) {
// The contact previously had a usable mailbox, now has
// a usable mailbox, it's the same mailbox, and its API
// versions haven't changed. Keep using the existing client
contactClient = requireNonNull(contactClients.get(c));
} else {
// The contact previously had a usable mailbox and now has
// a usable mailbox, but either it's a new mailbox or its
// API versions have changed. Replace the client
requireNonNull(contactClients.remove(c)).destroy();
contactClient = createAndStartClient(c);
clientReplaced = true;
}
} else {
// The client didn't previously have a usable mailbox but now
// has one. Create and start a client
contactClient = createAndStartClient(c);
}
} else if (wasContactMailboxUsable) {
// The client previously had a usable mailbox but no longer does.
// Destroy the existing client
requireNonNull(contactClients.remove(c)).destroy();
}
boolean wasAssignedToOwnMailboxForUpload =
wasOwnMailboxUsable && !wasContactMailboxUsable;
boolean willBeAssignedToOwnMailboxForUpload =
isOwnMailboxUsable && !isContactMailboxUsable;
boolean wasAssignedToContactMailboxForDownload =
!wasOwnMailboxUsable && wasContactMailboxUsable;
boolean willBeAssignedToContactMailboxForDownload =
!isOwnMailboxUsable && isContactMailboxUsable;
// Deassign the contact for upload/download if needed
if (wasAssignedToOwnMailboxForUpload &&
!willBeAssignedToOwnMailboxForUpload) {
requireNonNull(ownClient).deassignContactForUpload(c);
}
if (wasOwnMailboxUsable && !isOwnMailboxUsable) {
requireNonNull(ownClient).deassignContactForDownload(c);
}
// If the client for the contact's mailbox was replaced or destroyed
// above then we don't need to deassign the contact for download
if (wasAssignedToContactMailboxForDownload &&
!willBeAssignedToContactMailboxForDownload &&
!clientReplaced && isContactMailboxUsable) {
requireNonNull(contactClient).deassignContactForDownload(c);
}
// We never need to deassign the contact from the contact's mailbox for
// upload: this would only be needed if the contact's mailbox were no
// longer usable, in which case the client would already have been
// destroyed above. Thanks to the linter for spotting this
// Assign the contact for upload/download if needed
if (!wasAssignedToOwnMailboxForUpload &&
willBeAssignedToOwnMailboxForUpload) {
assignContactToOwnMailboxForUpload(c, u);
}
if (!wasOwnMailboxUsable && isOwnMailboxUsable) {
assignContactToOwnMailboxForDownload(c, u);
}
if ((!wasContactMailboxUsable || clientReplaced) &&
isContactMailboxUsable) {
assignContactToContactMailboxForUpload(c, contactClient, u);
}
if ((!wasAssignedToContactMailboxForDownload || clientReplaced) &&
willBeAssignedToContactMailboxForDownload) {
assignContactToContactMailboxForDownload(c, contactClient, u);
}
}
@EventExecutor
private void onOwnMailboxConnectionStatusChanged(MailboxStatus status) {
if (!online || ownProperties == null) return;
List<MailboxVersion> oldServerSupports =
ownProperties.getServerSupports();
List<MailboxVersion> newServerSupports = status.getServerSupports();
if (!oldServerSupports.equals(newServerSupports)) {
LOG.info("Our mailbox's supported API versions have changed");
// This potentially affects every assignment of contacts to
// mailboxes for upload and download, so just rebuild the clients
// and assignments from scratch
destroyClients();
createClients();
}
}
@EventExecutor
private void createAndStartClientForOwnMailbox() {
if (ownClient != null) throw new IllegalStateException();
ownClient = mailboxClientFactory.createOwnMailboxClient(
reachabilityMonitor, requireNonNull(ownProperties));
ownClient.start();
}
@EventExecutor
private MailboxClient createAndStartClient(ContactId c) {
MailboxClient client = mailboxClientFactory
.createContactMailboxClient(reachabilityMonitor);
MailboxClient old = contactClients.put(c, client);
if (old != null) throw new IllegalStateException();
client.start();
return client;
}
@EventExecutor
private void assignContactToOwnMailboxForDownload(ContactId c, Updates u) {
MailboxProperties localProps = getMailboxProperties(u.local);
requireNonNull(ownClient).assignContactForDownload(c,
requireNonNull(ownProperties),
requireNonNull(localProps.getOutboxId()));
}
@EventExecutor
private void assignContactToOwnMailboxForUpload(ContactId c, Updates u) {
MailboxProperties localProps = getMailboxProperties(u.local);
requireNonNull(ownClient).assignContactForUpload(c,
requireNonNull(ownProperties),
requireNonNull(localProps.getInboxId()));
}
@EventExecutor
private void assignContactToContactMailboxForDownload(ContactId c,
MailboxClient contactClient, Updates u) {
MailboxProperties remoteProps =
getMailboxProperties(requireNonNull(u.remote));
contactClient.assignContactForDownload(c, remoteProps,
requireNonNull(remoteProps.getInboxId()));
}
@EventExecutor
private void assignContactToContactMailboxForUpload(ContactId c,
MailboxClient contactClient, Updates u) {
MailboxProperties remoteProps =
getMailboxProperties(requireNonNull(u.remote));
contactClient.assignContactForUpload(c, remoteProps,
requireNonNull(remoteProps.getOutboxId()));
}
/**
* Returns the {@link MailboxProperties} included in the given update,
* which must be a {@link MailboxUpdateWithMailbox}.
*/
private MailboxProperties getMailboxProperties(MailboxUpdate update) {
if (!(update instanceof MailboxUpdateWithMailbox)) {
throw new IllegalArgumentException();
}
MailboxUpdateWithMailbox mailbox = (MailboxUpdateWithMailbox) update;
return mailbox.getMailboxProperties();
}
/**
* Returns true if we can use our own mailbox to communicate with the
* contact that sent the given update.
*/
private boolean isOwnMailboxUsable(
@Nullable MailboxProperties ownProperties,
@Nullable MailboxUpdate remote) {
if (ownProperties == null || remote == null) return false;
return isMailboxUsable(remote.getClientSupports(),
ownProperties.getServerSupports());
}
/**
* Returns true if we can use the contact's mailbox to communicate with
* the contact that sent the given update.
*/
private boolean isContactMailboxUsable(@Nullable MailboxUpdate remote) {
if (remote instanceof MailboxUpdateWithMailbox) {
MailboxUpdateWithMailbox remoteMailbox =
(MailboxUpdateWithMailbox) remote;
return isMailboxUsable(remoteMailbox.getClientSupports(),
remoteMailbox.getMailboxProperties().getServerSupports());
}
return false;
}
/**
* Returns true if we can communicate with a contact that has the given
* client-supported API versions via a mailbox with the given
* server-supported API versions.
*/
private boolean isMailboxUsable(List<MailboxVersion> contactClient,
List<MailboxVersion> server) {
return isClientCompatibleWithServer(contactClient, server)
&& isClientCompatibleWithServer(CLIENT_SUPPORTS, server);
}
/**
* Returns true if our client-supported API versions are compatible with
* our own mailbox's server-supported API versions.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isOwnMailboxUsable(MailboxProperties ownProperties) {
return isClientCompatibleWithServer(CLIENT_SUPPORTS,
ownProperties.getServerSupports());
}
/**
* A container for the latest {@link MailboxUpdate updates} sent to and
* received from a given contact.
*/
private static class Updates {
/**
* The latest update sent to the contact.
*/
private final MailboxUpdate local;
/**
* The latest update received from the contact, or null if no update
* has been received.
*/
@Nullable
private final MailboxUpdate remote;
private Updates(MailboxUpdate local, @Nullable MailboxUpdate remote) {
this.local = local;
this.remote = remote;
}
}
}

View File

@@ -1,250 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
import java.io.File;
import java.io.IOException;
import java.util.Queue;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
abstract class MailboxDownloadWorker implements MailboxWorker,
ConnectivityObserver, TorReachabilityObserver {
/**
* When the worker is started it waits for a connectivity check, then
* starts its first download cycle: checking for files to download,
* downloading and deleting the files, and checking again until all files
* have been downloaded and deleted.
* <p>
* The worker then waits for our Tor hidden service to be reachable before
* starting its second download cycle. This ensures that if a contact
* tried and failed to connect to our hidden service before it was
* reachable, and therefore uploaded a file to the mailbox instead, we'll
* find the file in the second download cycle.
*/
protected enum State {
CREATED,
CONNECTIVITY_CHECK,
DOWNLOAD_CYCLE_1,
WAITING_FOR_TOR,
DOWNLOAD_CYCLE_2,
FINISHED,
DESTROYED
}
protected static final Logger LOG =
getLogger(MailboxDownloadWorker.class.getName());
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor torReachabilityMonitor;
protected final MailboxApiCaller mailboxApiCaller;
protected final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
protected final MailboxProperties mailboxProperties;
protected final Object lock = new Object();
@GuardedBy("lock")
protected State state = State.CREATED;
@GuardedBy("lock")
@Nullable
protected Cancellable apiCall = null;
/**
* Creates the API call that starts the worker's download cycle.
*/
protected abstract ApiCall createApiCallForDownloadCycle();
MailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
this.connectivityChecker = connectivityChecker;
this.torReachabilityMonitor = torReachabilityMonitor;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) return;
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
torReachabilityMonitor.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.DOWNLOAD_CYCLE_1;
// Start first download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
createApiCallForDownloadCycle());
}
}
void onDownloadCycleFinished() {
boolean addObserver = false;
synchronized (lock) {
if (state == State.DOWNLOAD_CYCLE_1) {
LOG.info("First download cycle finished");
state = State.WAITING_FOR_TOR;
apiCall = null;
addObserver = true;
} else if (state == State.DOWNLOAD_CYCLE_2) {
LOG.info("Second download cycle finished");
state = State.FINISHED;
apiCall = null;
}
}
if (addObserver) {
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
torReachabilityMonitor.addOneShotObserver(this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) torReachabilityMonitor.removeObserver(this);
}
}
void downloadNextFile(Queue<FolderFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
if (queue.isEmpty()) {
// Check for files again, as new files may have arrived while
// we were downloading
apiCall = mailboxApiCaller.retryWithBackoff(
createApiCallForDownloadCycle());
} else {
FolderFile file = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() ->
apiCallDownloadFile(file, queue)));
}
}
}
private void apiCallDownloadFile(FolderFile file, Queue<FolderFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Downloading file");
File tempFile = mailboxFileManager.createTempFileForDownload();
try {
mailboxApi.getFile(mailboxProperties, file.folderId, file.fileId,
tempFile);
} catch (IOException | ApiException e) {
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
throw e;
} catch (TolerableFailureException e) {
// File not found - continue to the next file
LOG.warning("File does not exist");
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
downloadNextFile(queue);
return;
}
mailboxFileManager.handleDownloadedFile(tempFile);
deleteFile(file, queue);
}
private void deleteFile(FolderFile file, Queue<FolderFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
}
}
private void apiCallDeleteFile(FolderFile file, Queue<FolderFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
try {
mailboxApi.deleteFile(mailboxProperties, file.folderId,
file.fileId);
} catch (TolerableFailureException e) {
// File not found - continue to the next file
LOG.warning("File does not exist");
}
downloadNextFile(queue);
}
@Override
public void onTorReachable() {
LOG.info("Our Tor hidden service is reachable");
synchronized (lock) {
if (state != State.WAITING_FOR_TOR) return;
state = State.DOWNLOAD_CYCLE_2;
// Start second download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
createApiCallForDownloadCycle());
}
}
// Package access for testing
static class FolderFile {
final MailboxFolderId folderId;
final MailboxFileId fileId;
FolderFile(MailboxFolderId folderId, MailboxFileId fileId) {
this.folderId = folderId;
this.fileId = fileId;
}
}
}

View File

@@ -1,8 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -18,14 +16,6 @@ interface MailboxFileManager {
*/ */
File createTempFileForDownload() throws IOException; File createTempFileForDownload() throws IOException;
/**
* Creates a file to be uploaded to the given contact and writes any
* waiting data to the file. The IDs of any messages sent or acked will
* be added to the given {@link OutgoingSessionRecord}.
*/
File createAndWriteTempFileForUpload(ContactId contactId,
OutgoingSessionRecord sessionRecord) throws IOException;
/** /**
* Handles a file that has been downloaded. The file should be created * Handles a file that has been downloaded. The file should be created
* with {@link #createTempFileForDownload()}. * with {@link #createTempFileForDownload()}.

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
@@ -11,18 +10,13 @@ import org.briarproject.bramble.api.mailbox.MailboxDirectory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -36,7 +30,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID; import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH; import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.IoUtils.delete;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe @ThreadSafe
@@ -48,7 +41,6 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
// Package access for testing // Package access for testing
static final String DOWNLOAD_DIR_NAME = "downloads"; static final String DOWNLOAD_DIR_NAME = "downloads";
static final String UPLOAD_DIR_NAME = "uploads";
private final Executor ioExecutor; private final Executor ioExecutor;
private final PluginManager pluginManager; private final PluginManager pluginManager;
@@ -75,44 +67,14 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override @Override
public File createTempFileForDownload() throws IOException { public File createTempFileForDownload() throws IOException {
return createTempFile(DOWNLOAD_DIR_NAME);
}
@Override
public File createAndWriteTempFileForUpload(ContactId contactId,
OutgoingSessionRecord sessionRecord) throws IOException {
File f = createTempFile(UPLOAD_DIR_NAME);
// We shouldn't reach this point until the plugin has been started
SimplexPlugin plugin =
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, f.getAbsolutePath());
TransportConnectionWriter writer = plugin.createWriter(p);
if (writer == null) {
delete(f);
throw new IOException();
}
MailboxFileWriter decorated = new MailboxFileWriter(writer);
LOG.info("Writing file for upload");
connectionManager.manageOutgoingConnection(contactId, ID, decorated,
sessionRecord);
if (decorated.awaitDisposal()) {
// An exception was thrown during the session - delete the file
delete(f);
throw new IOException();
}
return f;
}
private File createTempFile(String dirName) throws IOException {
// Wait for orphaned files to be handled before creating new files // Wait for orphaned files to be handled before creating new files
try { try {
orphanLatch.await(); orphanLatch.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new IOException(e); throw new IOException(e);
} }
File dir = createDirectoryIfNeeded(dirName); File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
return File.createTempFile("mailbox", ".tmp", dir); return File.createTempFile("mailbox", ".tmp", downloadDir);
} }
private File createDirectoryIfNeeded(String name) throws IOException { private File createDirectoryIfNeeded(String name) throws IOException {
@@ -154,8 +116,6 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
// Wait for the transport to become active before handling orphaned
// files so that we can get the plugin from the plugin manager
if (e instanceof TransportActiveEvent) { if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(ID)) { if (t.getTransportId().equals(ID)) {
@@ -167,25 +127,17 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
/** /**
* This method is called at startup, as soon as the plugin is started, to * This method is called at startup, as soon as the plugin is started, to
* delete any files that were left in the upload directory at the last * handle any files that were left in the download directory at the last
* shutdown and handle any files that were left in the download directory. * shutdown.
*/ */
@IoExecutor @IoExecutor
private void handleOrphanedFiles() { private void handleOrphanedFiles() {
try { try {
File uploadDir = createDirectoryIfNeeded(UPLOAD_DIR_NAME);
File[] orphanedUploads = uploadDir.listFiles();
if (orphanedUploads != null) {
for (File f : orphanedUploads) delete(f);
}
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME); File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
File[] orphanedDownloads = downloadDir.listFiles(); File[] orphans = downloadDir.listFiles();
// Now that we've got the list of orphaned downloads, new files // Now that we've got the list of orphans, new files can be created
// can be created in the download directory
orphanLatch.countDown(); orphanLatch.countDown();
if (orphanedDownloads != null) { if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
for (File f : orphanedDownloads) handleDownloadedFile(f);
}
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -213,58 +165,9 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
delegate.dispose(exception, recognised); delegate.dispose(exception, recognised);
if (isHandlingComplete(exception, recognised)) { if (isHandlingComplete(exception, recognised)) {
LOG.info("Deleting downloaded file"); LOG.info("Deleting downloaded file");
delete(file); if (!file.delete()) {
} LOG.warning("Failed to delete downloaded file");
} }
}
private static class MailboxFileWriter
implements TransportConnectionWriter {
private final TransportConnectionWriter delegate;
private final BlockingQueue<Boolean> disposalResult =
new ArrayBlockingQueue<>(1);
private MailboxFileWriter(TransportConnectionWriter delegate) {
this.delegate = delegate;
}
@Override
public long getMaxLatency() {
return delegate.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return delegate.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return delegate.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public void dispose(boolean exception) throws IOException {
delegate.dispose(exception);
disposalResult.add(exception);
}
/**
* Waits for the delegate to be disposed and returns true if an
* exception occurred.
*/
private boolean awaitDisposal() {
try {
return disposalResult.take();
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for disposal");
return true;
} }
} }
} }

View File

@@ -9,12 +9,10 @@ import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -100,35 +98,34 @@ class MailboxManagerImpl implements MailboxManager {
@Override @Override
public boolean checkConnection() { public boolean checkConnection() {
List<MailboxVersion> versions = null; boolean success;
try { try {
MailboxProperties props = db.transactionWithNullableResult(true, MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties); mailboxSettingsManager::getOwnMailboxProperties);
if (props == null) throw new DbException(); if (props == null) throw new DbException();
versions = api.getServerSupports(props); success = api.checkStatus(props);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
// we don't treat this is a failure to record // we don't treat this is a failure to record
return false; return false;
} catch (IOException | MailboxApi.ApiException e) { } catch (IOException | MailboxApi.ApiException e) {
// we record this as a failure // we record this as a failure
success = false;
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
try { try {
recordCheckResult(versions); recordCheckResult(success);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
return versions != null; return success;
} }
private void recordCheckResult(@Nullable List<MailboxVersion> versions) private void recordCheckResult(boolean success) throws DbException {
throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
db.transaction(false, txn -> { db.transaction(false, txn -> {
if (versions != null) { if (success) {
mailboxSettingsManager mailboxSettingsManager.recordSuccessfulConnection(txn, now);
.recordSuccessfulConnection(txn, now, versions);
} else { } else {
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now); mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
} }

View File

@@ -4,22 +4,17 @@ import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.sync.validation.ValidationManager; import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -42,8 +37,6 @@ public class MailboxModule {
MailboxUpdateManager mailboxUpdateManager; MailboxUpdateManager mailboxUpdateManager;
@Inject @Inject
MailboxFileManager mailboxFileManager; MailboxFileManager mailboxFileManager;
@Inject
MailboxClientManager mailboxClientManager;
} }
@Provides @Provides
@@ -59,19 +52,13 @@ public class MailboxModule {
} }
@Provides @Provides
@Singleton
MailboxSettingsManager provideMailboxSettingsManager( MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) { MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager; return mailboxSettingsManager;
} }
@Provides @Provides
UrlConverter provideUrlConverter(UrlConverterImpl urlConverter) { MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
return urlConverter;
}
@Provides
MailboxApi provideMailboxApi(MailboxApiImpl mailboxApi) {
return mailboxApi; return mailboxApi;
} }
@@ -127,55 +114,4 @@ public class MailboxModule {
} }
return mailboxFileManager; return mailboxFileManager;
} }
@Provides
MailboxWorkerFactory provideMailboxWorkerFactory(
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
return mailboxWorkerFactory;
}
@Provides
MailboxClientFactory provideMailboxClientFactory(
MailboxClientFactoryImpl mailboxClientFactory) {
return mailboxClientFactory;
}
@Provides
MailboxApiCaller provideMailboxApiCaller(
MailboxApiCallerImpl mailboxApiCaller) {
return mailboxApiCaller;
}
@Provides
@Singleton
TorReachabilityMonitor provideTorReachabilityMonitor(
TorReachabilityMonitorImpl reachabilityMonitor) {
return reachabilityMonitor;
}
@Provides
@Singleton
MailboxClientManager provideMailboxClientManager(
@EventExecutor Executor eventExecutor,
@DatabaseExecutor Executor dbExecutor,
TransactionManager db,
ContactManager contactManager,
PluginManager pluginManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager,
MailboxClientFactory mailboxClientFactory,
TorReachabilityMonitor reachabilityMonitor,
FeatureFlags featureFlags,
LifecycleManager lifecycleManager,
EventBus eventBus) {
MailboxClientManager manager = new MailboxClientManager(eventExecutor,
dbExecutor, db, contactManager, pluginManager,
mailboxSettingsManager, mailboxUpdateManager,
mailboxClientFactory, reachabilityMonitor);
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerService(manager);
eventBus.addListener(manager);
}
return manager;
}
} }

View File

@@ -120,8 +120,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
db.transaction(false, txn -> { db.transaction(false, txn -> {
mailboxSettingsManager mailboxSettingsManager
.setOwnMailboxProperties(txn, ownerProperties); .setOwnMailboxProperties(txn, ownerProperties);
mailboxSettingsManager.recordSuccessfulConnection(txn, time, mailboxSettingsManager.recordSuccessfulConnection(txn, time);
ownerProperties.getServerSupports());
// A (possibly new) mailbox is paired. Reset message retransmission // A (possibly new) mailbox is paired. Reset message retransmission
// timers for contacts who doesn't have their own mailbox. This way, // timers for contacts who doesn't have their own mailbox. This way,
// data stranded on our old mailbox will be re-uploaded to our new. // data stranded on our old mailbox will be re-uploaded to our new.
@@ -178,9 +177,10 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
LOG.info("QR code is valid"); LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey); String onion = crypto.encodeOnion(onionPubKey);
String baseUrl = "http://" + onion + ".onion"; // TODO
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65); byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes); MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(onion, setupToken, new ArrayList<>()); return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
} }
} }

View File

@@ -20,13 +20,13 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ThreadSafe @Immutable
@NotNullByDefault @NotNullByDefault
class MailboxSettingsManagerImpl implements MailboxSettingsManager { class MailboxSettingsManagerImpl implements MailboxSettingsManager {
@@ -74,13 +74,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p) public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException { throws DbException {
Settings s = new Settings(); Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getOnion()); s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString()); s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
List<MailboxVersion> serverSupports = p.getServerSupports(); List<MailboxVersion> serverSupports = p.getServerSupports();
encodeServerSupports(serverSupports, s); int[] ints = new int[serverSupports.size() * 2];
int i = 0;
for (MailboxVersion v : serverSupports) {
ints[i++] = v.getMajor();
ints[i++] = v.getMinor();
}
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p); hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
} }
} }
@@ -89,10 +95,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
Settings s = new Settings(); Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, ""); s.put(SETTINGS_KEY_ONION, "");
s.put(SETTINGS_KEY_TOKEN, ""); s.put(SETTINGS_KEY_TOKEN, "");
s.put(SETTINGS_KEY_ATTEMPTS, "");
s.put(SETTINGS_KEY_LAST_ATTEMPT, "");
s.put(SETTINGS_KEY_LAST_SUCCESS, "");
s.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { for (MailboxHook hook : hooks) {
hook.mailboxUnpaired(txn); hook.mailboxUnpaired(txn);
@@ -117,38 +119,25 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
} }
@Override @Override
public void recordSuccessfulConnection(Transaction txn, long now, public void recordSuccessfulConnection(Transaction txn, long now)
List<MailboxVersion> versions) throws DbException { throws DbException {
// if we no longer have a paired mailbox, return
Settings oldSettings = Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE); settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = oldSettings.get(SETTINGS_KEY_ONION);
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
Settings s = new Settings(); Settings s = new Settings();
// record the successful connection
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now); s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now); s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0); s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
encodeServerSupports(versions, s);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
hook.serverSupportedVersionsReceived(txn, versions); MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
}
// broadcast status event
MailboxStatus status = new MailboxStatus(now, now, 0, versions);
txn.attach(new OwnMailboxConnectionStatusEvent(status)); txn.attach(new OwnMailboxConnectionStatusEvent(status));
} }
@Override @Override
public void recordFailedConnectionAttempt(Transaction txn, long now) public void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException { throws DbException {
// if we no longer have a paired mailbox, return
Settings oldSettings = Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE); settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = oldSettings.get(SETTINGS_KEY_ONION);
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0); int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0); long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
Settings newSettings = new Settings(); Settings newSettings = new Settings();
@@ -182,17 +171,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
return filename; return filename;
} }
private void encodeServerSupports(List<MailboxVersion> serverSupports,
Settings s) {
int[] ints = new int[serverSupports.size() * 2];
int i = 0;
for (MailboxVersion v : serverSupports) {
ints[i++] = v.getMajor();
ints[i++] = v.getMinor();
}
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
}
private List<MailboxVersion> parseServerSupports(Settings s) private List<MailboxVersion> parseServerSupports(Settings s)
throws DbException { throws DbException {
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList(); if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();

View File

@@ -25,9 +25,6 @@ import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent; import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
@@ -41,7 +38,6 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -49,9 +45,6 @@ import java.util.Map.Entry;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
@NotNullByDefault @NotNullByDefault
@@ -123,7 +116,7 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts // Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
addingContact(txn, c, false); addingContact(txn, c);
} }
} }
@@ -139,17 +132,6 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
@Override @Override
public void addingContact(Transaction txn, Contact c) throws DbException { public void addingContact(Transaction txn, Contact c) throws DbException {
addingContact(txn, c, true);
}
/**
* @param attachEvent True if a {@link MailboxUpdateSentToNewContactEvent}
* should be attached to the transaction. We should only do this when
* adding a new contact, not when setting up this client for an existing
* contact.
*/
private void addingContact(Transaction txn, Contact c, boolean attachEvent)
throws DbException {
// Create a group to share with the contact // Create a group to share with the contact
Group g = getContactGroup(c); Group g = getContactGroup(c);
db.addGroup(txn, g); db.addGroup(txn, g);
@@ -161,17 +143,13 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
clientHelper.setContactId(txn, g.getId(), c.getId()); clientHelper.setContactId(txn, g.getId(), c.getId());
MailboxProperties ownProps = MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn); mailboxSettingsManager.getOwnMailboxProperties(txn);
MailboxUpdate u;
if (ownProps != null) { if (ownProps != null) {
// We are paired, create and send props to the newly added contact // We are paired, create and send props to the newly added contact
u = createAndSendUpdateWithMailbox(txn, c, createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
ownProps.getServerSupports(), ownProps.getOnion()); ownProps.getOnion());
} else { } else {
// Not paired, but we still want to get our clientSupports sent // Not paired, but we still want to get our clientSupports sent
u = sendUpdateNoMailbox(txn, c); sendUpdateNoMailbox(txn, c);
}
if (attachEvent) {
txn.attach(new MailboxUpdateSentToNewContactEvent(c.getId(), u));
} }
} }
@@ -181,103 +159,18 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
} }
@Override @Override
public void mailboxPaired(Transaction txn, MailboxProperties p) public void mailboxPaired(Transaction txn, String ownOnion,
throws DbException { List<MailboxVersion> serverSupports) throws DbException {
Map<ContactId, MailboxUpdateWithMailbox> localUpdates = new HashMap<>();
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
MailboxUpdateWithMailbox u = createAndSendUpdateWithMailbox(txn, c, createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
p.getServerSupports(), p.getOnion());
localUpdates.put(c.getId(), u);
}
txn.attach(new MailboxPairedEvent(p, localUpdates));
// Store the list of server-supported versions
try {
storeSentServerSupports(txn, p.getServerSupports());
} catch (FormatException e) {
throw new DbException();
} }
} }
@Override @Override
public void mailboxUnpaired(Transaction txn) throws DbException { public void mailboxUnpaired(Transaction txn) throws DbException {
Map<ContactId, MailboxUpdate> localUpdates = new HashMap<>();
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
MailboxUpdate u = sendUpdateNoMailbox(txn, c); sendUpdateNoMailbox(txn, c);
localUpdates.put(c.getId(), u);
} }
txn.attach(new MailboxUnpairedEvent(localUpdates));
// Remove the list of server-supported versions
try {
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_SERVER_SUPPORTS, NULL_VALUE));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
} catch (FormatException e) {
throw new DbException();
}
}
@Override
public void serverSupportedVersionsReceived(Transaction txn,
List<MailboxVersion> serverSupports) throws DbException {
try {
List<MailboxVersion> oldServerSupports =
loadSentServerSupports(txn);
if (serverSupports.equals(oldServerSupports)) return;
storeSentServerSupports(txn, serverSupports);
for (Contact c : db.getContacts(txn)) {
Group contactGroup = getContactGroup(c);
LatestUpdate latest =
findLatest(txn, contactGroup.getId(), true);
// This method should only be called when we have a mailbox,
// in which case we should have sent a local update to every
// contact
if (latest == null) throw new DbException();
BdfList body =
clientHelper.getMessageAsList(txn, latest.messageId);
MailboxUpdate oldUpdate = parseUpdate(body);
if (!oldUpdate.hasMailbox()) throw new DbException();
MailboxUpdateWithMailbox newUpdate = updateServerSupports(
(MailboxUpdateWithMailbox) oldUpdate, serverSupports);
storeMessageReplaceLatest(txn, contactGroup.getId(), newUpdate,
latest);
}
} catch (FormatException e) {
throw new DbException();
}
}
private void storeSentServerSupports(Transaction txn,
List<MailboxVersion> serverSupports)
throws DbException, FormatException {
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_SERVER_SUPPORTS,
encodeSupportsList(serverSupports)));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
}
private List<MailboxVersion> loadSentServerSupports(Transaction txn)
throws DbException, FormatException {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
localGroup.getId());
BdfList serverSupports =
meta.getOptionalList(GROUP_KEY_SENT_SERVER_SUPPORTS);
if (serverSupports == null) return emptyList();
return clientHelper.parseMailboxVersionList(serverSupports);
}
/**
* Returns a new {@link MailboxUpdateWithMailbox} that updates the list
* of server-supported API versions in the given
* {@link MailboxUpdateWithMailbox}.
*/
private MailboxUpdateWithMailbox updateServerSupports(
MailboxUpdateWithMailbox old, List<MailboxVersion> serverSupports) {
MailboxProperties oldProps = old.getMailboxProperties();
MailboxProperties newProps = new MailboxProperties(oldProps.getOnion(),
oldProps.getAuthToken(), serverSupports,
requireNonNull(oldProps.getInboxId()),
requireNonNull(oldProps.getOutboxId()));
return new MailboxUpdateWithMailbox(old.getClientSupports(), newProps);
} }
@Override @Override
@@ -346,19 +239,19 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
* supported Mailbox API version(s). All of which the contact needs to * supported Mailbox API version(s). All of which the contact needs to
* communicate with our Mailbox. * communicate with our Mailbox.
*/ */
private MailboxUpdateWithMailbox createAndSendUpdateWithMailbox( private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
Transaction txn, Contact c, List<MailboxVersion> serverSupports, List<MailboxVersion> serverSupports, String ownOnion)
String ownOnion) throws DbException { throws DbException {
MailboxProperties properties = new MailboxProperties(ownOnion, String baseUrl = "http://" + ownOnion + ".onion"; // TODO
MailboxProperties properties = new MailboxProperties(baseUrl,
new MailboxAuthToken(crypto.generateUniqueId().getBytes()), new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
serverSupports, serverSupports,
new MailboxFolderId(crypto.generateUniqueId().getBytes()), new MailboxFolderId(crypto.generateUniqueId().getBytes()),
new MailboxFolderId(crypto.generateUniqueId().getBytes())); new MailboxFolderId(crypto.generateUniqueId().getBytes()));
MailboxUpdateWithMailbox u = MailboxUpdate u =
new MailboxUpdateWithMailbox(clientSupports, properties); new MailboxUpdateWithMailbox(clientSupports, properties);
Group g = getContactGroup(c); Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), u); storeMessageReplaceLatest(txn, g.getId(), u);
return u;
} }
/** /**
@@ -367,12 +260,11 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
* Mailbox that they can use. It still includes the list of Mailbox API * Mailbox that they can use. It still includes the list of Mailbox API
* version(s) that we support as a client. * version(s) that we support as a client.
*/ */
private MailboxUpdate sendUpdateNoMailbox(Transaction txn, Contact c) private void sendUpdateNoMailbox(Transaction txn, Contact c)
throws DbException { throws DbException {
Group g = getContactGroup(c); Group g = getContactGroup(c);
MailboxUpdate u = new MailboxUpdate(clientSupports); MailboxUpdate u = new MailboxUpdate(clientSupports);
storeMessageReplaceLatest(txn, g.getId(), u); storeMessageReplaceLatest(txn, g.getId(), u);
return u;
} }
@Nullable @Nullable
@@ -397,27 +289,21 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
MailboxUpdate u) throws DbException { MailboxUpdate u) throws DbException {
try { try {
LatestUpdate latest = findLatest(txn, g, true); LatestUpdate latest = findLatest(txn, g, true);
storeMessageReplaceLatest(txn, g, u, latest); long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u));
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
if (latest != null) {
db.removeMessage(txn, latest.messageId);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} }
} }
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
MailboxUpdate u, @Nullable LatestUpdate latest)
throws DbException, FormatException {
long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u));
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
if (latest != null) {
db.removeMessage(txn, latest.messageId);
}
}
@Nullable @Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local) private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
throws DbException, FormatException { throws DbException, FormatException {

View File

@@ -1,464 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.util.IoUtils.delete;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
EventListener {
/**
* When the worker is started it checks for data to send. If data is ready
* to send, the worker waits for a connectivity check, then writes and
* uploads a file and checks again for data to send.
* <p>
* If data is due to be sent at some time in the future, the worker
* schedules a wakeup for that time and also listens for events indicating
* that new data may be ready to send.
* <p>
* If there's no data to send, the worker listens for events indicating
* that new data may be ready to send.
* <p>
* Whenever we're directly connected to the contact, the worker doesn't
* check for data to send or start connectivity checks until the contact
* disconnects. However, if the worker has already started writing and
* uploading a file when the contact connects, the worker will finish the
* upload.
*/
private enum State {
CREATED,
CONNECTED_TO_CONTACT,
CHECKING_FOR_DATA,
WAITING_FOR_DATA,
CONNECTIVITY_CHECK,
WRITING_UPLOADING,
DESTROYED
}
private static final Logger LOG =
getLogger(MailboxUploadWorker.class.getName());
/**
* When we're waiting for data to send and an event indicates that new data
* may have become available, wait this long before checking the DB. This
* should help to avoid creating lots of small files when several acks or
* messages become available to send in a short period (eg when reading a
* file downloaded from a mailbox).
* <p>
* Package access for testing.
*/
static final long CHECK_DELAY_MS = 5_000;
/**
* How long to wait before retrying when an exception occurs while writing
* a file.
* <p>
* Package access for testing.
*/
static final long RETRY_DELAY_MS = MINUTES.toMillis(1);
private final Executor ioExecutor;
private final DatabaseComponent db;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final EventBus eventBus;
private final ConnectionRegistry connectionRegistry;
private final ConnectivityChecker connectivityChecker;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxProperties mailboxProperties;
private final MailboxFolderId folderId;
private final ContactId contactId;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable wakeupTask = null, checkTask = null, apiCall = null;
@GuardedBy("lock")
@Nullable
private File file = null;
MailboxUploadWorker(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
ConnectionRegistry connectionRegistry,
ConnectivityChecker connectivityChecker,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties,
MailboxFolderId folderId,
ContactId contactId) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.connectionRegistry = connectionRegistry;
this.connectivityChecker = connectivityChecker;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
this.folderId = folderId;
this.contactId = contactId;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) return;
state = State.CHECKING_FOR_DATA;
}
ioExecutor.execute(this::checkForDataToSend);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable wakeupTask, checkTask, apiCall;
File file;
synchronized (lock) {
state = State.DESTROYED;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
checkTask = this.checkTask;
this.checkTask = null;
apiCall = this.apiCall;
this.apiCall = null;
file = this.file;
this.file = null;
}
if (wakeupTask != null) wakeupTask.cancel();
if (checkTask != null) checkTask.cancel();
if (apiCall != null) apiCall.cancel();
if (file != null) delete(file);
connectivityChecker.removeObserver(this);
eventBus.removeListener(this);
}
@IoExecutor
private void checkForDataToSend() {
synchronized (lock) {
checkTask = null;
if (state != State.CHECKING_FOR_DATA) return;
// Check whether we're directly connected to the contact. Calling
// this while holding the lock isn't ideal, but it avoids races
if (connectionRegistry.isConnected(contactId)) {
state = State.CONNECTED_TO_CONTACT;
return;
}
}
LOG.info("Checking for data to send");
try {
db.transaction(true, txn -> {
long nextSendTime;
if (db.containsAcksToSend(txn, contactId)) {
nextSendTime = 0L;
} else {
nextSendTime = db.getNextSendTime(txn, contactId,
MAX_LATENCY);
}
// Handle the result on the event executor to avoid races with
// incoming events
txn.attach(() -> handleNextSendTime(nextSendTime));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void handleNextSendTime(long nextSendTime) {
if (nextSendTime == Long.MAX_VALUE) {
// Nothing is sendable now or due to be sent in the future. Wait
// for an event indicating that new data may be ready to send
waitForDataToSend();
} else {
// Work out the delay until data's ready to send (may be negative)
long delay = nextSendTime - clock.currentTimeMillis();
if (delay > 0) {
// Schedule a wakeup when data will be ready to send. If an
// event is received in the meantime indicating that new data
// may be ready to send, we'll cancel the wakeup
scheduleWakeup(delay);
} else {
// Data is ready to send now
checkConnectivity();
}
}
}
@EventExecutor
private void waitForDataToSend() {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.WAITING_FOR_DATA;
LOG.info("Waiting for data to send");
}
}
@EventExecutor
private void scheduleWakeup(long delay) {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.WAITING_FOR_DATA;
if (LOG.isLoggable(INFO)) {
LOG.info("Scheduling wakeup in " + delay + " ms");
}
wakeupTask = taskScheduler.schedule(this::wakeUp, ioExecutor,
delay, MILLISECONDS);
}
}
@IoExecutor
private void wakeUp() {
LOG.info("Woke up");
synchronized (lock) {
wakeupTask = null;
if (state != State.WAITING_FOR_DATA) return;
state = State.CHECKING_FOR_DATA;
}
checkForDataToSend();
}
@EventExecutor
private void checkConnectivity() {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.CONNECTIVITY_CHECK;
}
LOG.info("Checking connectivity");
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.WRITING_UPLOADING;
}
ioExecutor.execute(this::writeAndUploadFile);
}
@IoExecutor
private void writeAndUploadFile() {
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
}
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
File file;
try {
file = mailboxFileManager.createAndWriteTempFileForUpload(
contactId, sessionRecord);
} catch (IOException e) {
logException(LOG, WARNING, e);
// Try again after a delay
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
checkTask = taskScheduler.schedule(this::checkForDataToSend,
ioExecutor, RETRY_DELAY_MS, MILLISECONDS);
}
return;
}
boolean deleteFile = false;
synchronized (lock) {
if (state == State.WRITING_UPLOADING) {
this.file = file;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallUploadFile(file,
sessionRecord)));
} else {
deleteFile = true;
}
}
if (deleteFile) delete(file);
}
@IoExecutor
private void apiCallUploadFile(File file,
OutgoingSessionRecord sessionRecord)
throws IOException, ApiException {
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
}
LOG.info("Uploading file");
mailboxApi.addFile(mailboxProperties, folderId, file);
markMessagesSentOrAcked(sessionRecord);
delete(file);
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
apiCall = null;
this.file = null;
}
checkForDataToSend();
}
private void markMessagesSentOrAcked(OutgoingSessionRecord sessionRecord) {
Collection<MessageId> acked = sessionRecord.getAckedIds();
Collection<MessageId> sent = sessionRecord.getSentIds();
try {
db.transaction(false, txn -> {
if (!acked.isEmpty()) {
db.setAckSent(txn, contactId, acked);
}
if (!sent.isEmpty()) {
db.setMessagesSent(txn, contactId, sent, MAX_LATENCY);
}
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessageToAckEvent) {
MessageToAckEvent m = (MessageToAckEvent) e;
if (m.getContactId().equals(contactId)) {
LOG.info("Message to ack");
onDataToSend();
}
} else if (e instanceof MessageSharedEvent) {
MessageSharedEvent m = (MessageSharedEvent) e;
// If the contact is present in the map (ie the value is not null)
// and the value is true, the message's group is shared with the
// contact and therefore the message may now be sendable
if (m.getGroupVisibility().get(contactId) == TRUE) {
LOG.info("Message shared");
onDataToSend();
}
} else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED &&
g.getAffectedContacts().contains(contactId)) {
LOG.info("Group shared");
onDataToSend();
}
} else if (e instanceof ContactConnectedEvent) {
ContactConnectedEvent c = (ContactConnectedEvent) e;
if (c.getContactId().equals(contactId)) {
LOG.info("Contact connected");
onContactConnected();
}
} else if (e instanceof ContactDisconnectedEvent) {
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
if (c.getContactId().equals(contactId)) {
LOG.info("Contact disconnected");
onContactDisconnected();
}
}
}
@EventExecutor
private void onDataToSend() {
Cancellable wakeupTask;
synchronized (lock) {
if (state != State.WAITING_FOR_DATA) return;
state = State.CHECKING_FOR_DATA;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
// Delay the check to avoid creating lots of small files
checkTask = taskScheduler.schedule(this::checkForDataToSend,
ioExecutor, CHECK_DELAY_MS, MILLISECONDS);
}
// If we had scheduled a wakeup when data was due to be sent, cancel it
if (wakeupTask != null) wakeupTask.cancel();
}
@EventExecutor
private void onContactConnected() {
Cancellable wakeupTask = null, checkTask = null;
synchronized (lock) {
if (state == State.DESTROYED) return;
// If we're checking for data to send, waiting for data to send,
// or checking connectivity then wait until we disconnect from
// the contact before proceeding. If we're writing or uploading
// a file then continue
if (state == State.CHECKING_FOR_DATA ||
state == State.WAITING_FOR_DATA ||
state == State.CONNECTIVITY_CHECK) {
state = State.CONNECTED_TO_CONTACT;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
checkTask = this.checkTask;
this.checkTask = null;
}
}
if (wakeupTask != null) wakeupTask.cancel();
if (checkTask != null) checkTask.cancel();
}
@EventExecutor
private void onContactDisconnected() {
synchronized (lock) {
if (state != State.CONNECTED_TO_CONTACT) return;
state = State.CHECKING_FOR_DATA;
}
ioExecutor.execute(this::checkForDataToSend);
}
}

View File

@@ -1,31 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
interface MailboxWorkerFactory {
MailboxWorker createUploadWorker(ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId);
MailboxWorker createDownloadWorkerForContactMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
MailboxWorker createDownloadWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
MailboxWorker createContactListWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
MailboxProperties properties);
}

View File

@@ -1,101 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
private final Executor ioExecutor;
private final DatabaseComponent db;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final EventBus eventBus;
private final ConnectionRegistry connectionRegistry;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxUpdateManager mailboxUpdateManager;
@Inject
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
ConnectionRegistry connectionRegistry,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxUpdateManager mailboxUpdateManager) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.connectionRegistry = connectionRegistry;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxUpdateManager = mailboxUpdateManager;
}
@Override
public MailboxWorker createUploadWorker(
ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId) {
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
clock, taskScheduler, eventBus, connectionRegistry,
connectivityChecker, mailboxApiCaller, mailboxApi,
mailboxFileManager, properties, folderId, contactId);
eventBus.addListener(worker);
return worker;
}
@Override
public MailboxWorker createDownloadWorkerForContactMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
return new ContactMailboxDownloadWorker(connectivityChecker,
reachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, properties);
}
@Override
public MailboxWorker createDownloadWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
return new OwnMailboxDownloadWorker(connectivityChecker,
reachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, properties);
}
@Override
public MailboxWorker createContactListWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
MailboxProperties properties) {
OwnMailboxContactListWorker worker = new OwnMailboxContactListWorker(
ioExecutor, db, eventBus, connectivityChecker, mailboxApiCaller,
mailboxApi, mailboxUpdateManager, properties);
eventBus.addListener(worker);
return worker;
}
}

View File

@@ -1,222 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
class OwnMailboxClient implements MailboxClient, ConnectivityObserver {
/**
* How often to check our own mailbox's connectivity.
* <p>
* Package access for testing.
*/
static final long CONNECTIVITY_CHECK_INTERVAL_MS = HOURS.toMillis(1);
private static final Logger LOG =
getLogger(OwnMailboxClient.class.getName());
private final MailboxWorkerFactory workerFactory;
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor reachabilityMonitor;
private final TaskScheduler taskScheduler;
private final Executor ioExecutor;
private final MailboxProperties properties;
private final MailboxWorker contactListWorker;
private final Object lock = new Object();
/**
* Upload workers: one worker per contact assigned for upload.
*/
@GuardedBy("lock")
private final Map<ContactId, MailboxWorker> uploadWorkers = new HashMap<>();
/**
* Download worker: shared between all contacts assigned for download.
* Null if no contacts are assigned for download.
*/
@GuardedBy("lock")
@Nullable
private MailboxWorker downloadWorker = null;
/**
* IDs of contacts assigned for download, so that we know when to
* create/destroy the download worker.
*/
@GuardedBy("lock")
private final Set<ContactId> assignedForDownload = new HashSet<>();
/**
* Scheduled task for periodically checking whether the mailbox is
* reachable.
*/
@GuardedBy("lock")
@Nullable
private Cancellable connectivityTask = null;
@GuardedBy("lock")
private boolean destroyed = false;
OwnMailboxClient(MailboxWorkerFactory workerFactory,
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
TaskScheduler taskScheduler,
@IoExecutor Executor ioExecutor,
MailboxProperties properties) {
if (!properties.isOwner()) throw new IllegalArgumentException();
this.workerFactory = workerFactory;
this.connectivityChecker = connectivityChecker;
this.reachabilityMonitor = reachabilityMonitor;
this.taskScheduler = taskScheduler;
this.ioExecutor = ioExecutor;
this.properties = properties;
contactListWorker = workerFactory.createContactListWorkerForOwnMailbox(
connectivityChecker, properties);
}
@Override
public void start() {
LOG.info("Started");
checkConnectivity();
contactListWorker.start();
}
@Override
public void destroy() {
LOG.info("Destroyed");
List<MailboxWorker> uploadWorkers;
MailboxWorker downloadWorker;
Cancellable connectivityTask;
synchronized (lock) {
uploadWorkers = new ArrayList<>(this.uploadWorkers.values());
this.uploadWorkers.clear();
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
connectivityTask = this.connectivityTask;
this.connectivityTask = null;
destroyed = true;
}
// Destroy the workers (with apologies to Mr Marx and Mr Engels)
for (MailboxWorker worker : uploadWorkers) worker.destroy();
if (downloadWorker != null) downloadWorker.destroy();
// If a connectivity check is scheduled, cancel it
if (connectivityTask != null) connectivityTask.cancel();
contactListWorker.destroy();
// The connectivity checker belongs to the client, so it should be
// destroyed. The Tor reachability monitor is shared between clients,
// so it should not be destroyed
connectivityChecker.destroy();
}
@Override
public void assignContactForUpload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for upload");
if (!properties.isOwner()) throw new IllegalArgumentException();
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
connectivityChecker, properties, folderId, contactId);
synchronized (lock) {
MailboxWorker old = uploadWorkers.put(contactId, uploadWorker);
if (old != null) throw new IllegalStateException();
}
uploadWorker.start();
}
@Override
public void deassignContactForUpload(ContactId contactId) {
LOG.info("Contact deassigned for upload");
MailboxWorker uploadWorker;
synchronized (lock) {
uploadWorker = uploadWorkers.remove(contactId);
}
if (uploadWorker != null) uploadWorker.destroy();
}
@Override
public void assignContactForDownload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for download");
if (!properties.isOwner()) throw new IllegalArgumentException();
// Create a download worker if we don't already have one. The worker
// will use the API to discover which folders have files to download,
// so it doesn't need to track the set of assigned contacts
MailboxWorker toStart = null;
synchronized (lock) {
if (!assignedForDownload.add(contactId)) {
throw new IllegalStateException();
}
if (downloadWorker == null) {
toStart = workerFactory.createDownloadWorkerForOwnMailbox(
connectivityChecker, reachabilityMonitor, properties);
downloadWorker = toStart;
}
}
if (toStart != null) toStart.start();
}
@Override
public void deassignContactForDownload(ContactId contactId) {
LOG.info("Contact deassigned for download");
// If there are no more contacts assigned for download, destroy the
// download worker
MailboxWorker toDestroy = null;
synchronized (lock) {
if (!assignedForDownload.remove(contactId)) {
throw new IllegalStateException();
}
if (assignedForDownload.isEmpty()) {
toDestroy = downloadWorker;
downloadWorker = null;
}
}
if (toDestroy != null) toDestroy.destroy();
}
private void checkConnectivity() {
synchronized (lock) {
if (destroyed) return;
connectivityTask = null;
}
connectivityChecker.checkConnectivity(properties, this);
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
boolean removeObserver;
synchronized (lock) {
removeObserver = destroyed;
}
if (removeObserver) connectivityChecker.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
synchronized (lock) {
if (destroyed) return;
connectivityTask = taskScheduler.schedule(this::checkConnectivity,
ioExecutor, CONNECTIVITY_CHECK_INTERVAL_MS, MILLISECONDS);
}
}
}

View File

@@ -4,17 +4,14 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -31,7 +28,6 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private final TransactionManager db; private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
@Inject
OwnMailboxConnectivityChecker(Clock clock, OwnMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller, MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi, MailboxApi mailboxApi,
@@ -59,13 +55,11 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private boolean checkConnectivityAndStoreResult( private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException { MailboxProperties properties) throws DbException {
try { try {
LOG.info("Checking whether own mailbox is reachable"); if (!mailboxApi.checkStatus(properties)) throw new ApiException();
List<MailboxVersion> serverSupports =
mailboxApi.getServerSupports(properties);
LOG.info("Own mailbox is reachable"); LOG.info("Own mailbox is reachable");
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager db.transaction(false, txn -> mailboxSettingsManager
.recordSuccessfulConnection(txn, now, serverSupports)); .recordSuccessfulConnection(txn, now));
// Call the observers and cache the result // Call the observers and cache the result
onConnectivityCheckSucceeded(now); onConnectivityCheckSucceeded(now);
return false; // Don't retry return false; // Don't retry

View File

@@ -1,369 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class OwnMailboxContactListWorker
implements MailboxWorker, ConnectivityObserver, EventListener {
/**
* When the worker is started it waits for a connectivity check, then
* fetches the remote contact list and compares it to the local contact
* list.
* <p>
* Any contacts that are missing from the remote list are added to the
* mailbox's contact list, while any contacts that are missing from the
* local list are removed from the mailbox's contact list.
* <p>
* Once the remote contact list has been brought up to date, the worker
* waits for events indicating that contacts have been added or removed.
* Each time an event is received, the worker updates the mailbox's
* contact list and then goes back to waiting.
*/
private enum State {
CREATED,
CONNECTIVITY_CHECK,
FETCHING_CONTACT_LIST,
UPDATING_CONTACT_LIST,
WAITING_FOR_CHANGES,
DESTROYED
}
private static final Logger LOG =
getLogger(OwnMailboxContactListWorker.class.getName());
private final Executor ioExecutor;
private final DatabaseComponent db;
private final EventBus eventBus;
private final ConnectivityChecker connectivityChecker;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxUpdateManager mailboxUpdateManager;
private final MailboxProperties mailboxProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable apiCall = null;
/**
* A queue of updates waiting to be applied to the remote contact list.
*/
@GuardedBy("lock")
private final Queue<Update> updates = new LinkedList<>();
OwnMailboxContactListWorker(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
EventBus eventBus,
ConnectivityChecker connectivityChecker,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxUpdateManager mailboxUpdateManager,
MailboxProperties mailboxProperties) {
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
this.ioExecutor = ioExecutor;
this.db = db;
this.connectivityChecker = connectivityChecker;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxUpdateManager = mailboxUpdateManager;
this.mailboxProperties = mailboxProperties;
this.eventBus = eventBus;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
if (state != State.CREATED) return;
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
eventBus.removeListener(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.FETCHING_CONTACT_LIST;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallFetchContactList));
}
}
@IoExecutor
private void apiCallFetchContactList() throws IOException, ApiException {
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
}
LOG.info("Fetching remote contact list");
Collection<ContactId> remote =
mailboxApi.getContacts(mailboxProperties);
ioExecutor.execute(() -> loadLocalContactList(remote));
}
@IoExecutor
private void loadLocalContactList(Collection<ContactId> remote) {
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
apiCall = null;
}
LOG.info("Loading local contact list");
try {
db.transaction(true, txn -> {
Collection<Contact> local = db.getContacts(txn);
// Handle the result on the event executor to avoid races with
// incoming events
txn.attach(() -> reconcileContactLists(local, remote));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void reconcileContactLists(Collection<Contact> local,
Collection<ContactId> remote) {
Set<ContactId> localIds = new HashSet<>();
for (Contact c : local) localIds.add(c.getId());
remote = new HashSet<>(remote);
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
for (ContactId c : localIds) {
if (!remote.contains(c)) updates.add(new Update(true, c));
}
for (ContactId c : remote) {
if (!localIds.contains(c)) updates.add(new Update(false, c));
}
if (updates.isEmpty()) {
LOG.info("Contact list is up to date");
state = State.WAITING_FOR_CHANGES;
} else {
if (LOG.isLoggable(INFO)) {
LOG.info(updates.size() + " updates to apply");
}
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
@IoExecutor
private void updateContactList() {
Update update;
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
update = updates.poll();
if (update == null) {
LOG.info("No more updates to process");
state = State.WAITING_FOR_CHANGES;
apiCall = null;
return;
}
}
if (update.add) loadMailboxProperties(update.contactId);
else removeContact(update.contactId);
}
@IoExecutor
private void loadMailboxProperties(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Loading mailbox properties for contact");
try {
MailboxUpdate mailboxUpdate = db.transactionWithResult(true, txn ->
mailboxUpdateManager.getLocalUpdate(txn, c));
if (mailboxUpdate instanceof MailboxUpdateWithMailbox) {
addContact(c, (MailboxUpdateWithMailbox) mailboxUpdate);
} else {
// Our own mailbox was concurrently unpaired. This worker will
// be destroyed soon, so we can stop here
LOG.info("Own mailbox was unpaired");
}
} catch (NoSuchContactException e) {
// Contact was removed concurrently. Move on to the next update.
// Later we may process a removal update for this contact, which
// was never added to the mailbox's contact list. The removal API
// call should fail safely with a TolerableFailureException
LOG.info("No such contact");
updateContactList();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@IoExecutor
private void addContact(ContactId c, MailboxUpdateWithMailbox withMailbox) {
MailboxProperties props = withMailbox.getMailboxProperties();
MailboxContact contact = new MailboxContact(c, props.getAuthToken(),
requireNonNull(props.getInboxId()),
requireNonNull(props.getOutboxId()));
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallAddContact(contact)));
}
}
@IoExecutor
private void apiCallAddContact(MailboxContact contact)
throws IOException, ApiException, TolerableFailureException {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Adding contact to remote contact list");
mailboxApi.addContact(mailboxProperties, contact);
updateContactList();
}
@IoExecutor
private void removeContact(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallRemoveContact(c)));
}
}
@IoExecutor
private void apiCallRemoveContact(ContactId c)
throws IOException, ApiException {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Removing contact from remote contact list");
try {
mailboxApi.deleteContact(mailboxProperties, c);
} catch (TolerableFailureException e) {
// Catch this so we can continue to the next update
LOG.warning("Contact does not exist");
}
updateContactList();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added");
onContactAdded(((ContactAddedEvent) e).getContactId());
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed");
onContactRemoved(((ContactRemovedEvent) e).getContactId());
}
}
@EventExecutor
private void onContactAdded(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST &&
state != State.WAITING_FOR_CHANGES) {
return;
}
updates.add(new Update(true, c));
if (state == State.WAITING_FOR_CHANGES) {
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
@EventExecutor
private void onContactRemoved(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST &&
state != State.WAITING_FOR_CHANGES) {
return;
}
updates.add(new Update(false, c));
if (state == State.WAITING_FOR_CHANGES) {
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
/**
* An update that should be applied to the remote contact list.
*/
private static class Update {
/**
* True if the contact should be added, false if the contact should be
* removed.
*/
private final boolean add;
private final ContactId contactId;
private Update(boolean add, ContactId contactId) {
this.add = add;
this.contactId = contactId;
}
}
}

View File

@@ -1,148 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.Collections.shuffle;
import static java.util.logging.Level.INFO;
@ThreadSafe
@NotNullByDefault
class OwnMailboxDownloadWorker extends MailboxDownloadWorker {
/**
* The maximum number of files that will be downloaded before checking
* again for folders with available files. This ensures that if a file
* arrives during a download cycle, its folder will be checked within a
* reasonable amount of time even if some other folder has a very large
* number of files.
* <p>
* Package access for testing.
*/
static final int MAX_ROUND_ROBIN_FILES = 1000;
OwnMailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
mailboxApi, mailboxFileManager, mailboxProperties);
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
}
@Override
protected ApiCall createApiCallForDownloadCycle() {
return new SimpleApiCall(this::apiCallListFolders);
}
private void apiCallListFolders() throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing folders with available files");
List<MailboxFolderId> folders =
mailboxApi.getFolders(mailboxProperties);
if (folders.isEmpty()) onDownloadCycleFinished();
else listNextFolder(new LinkedList<>(folders), new HashMap<>());
}
/**
* Removes the next folder from `queue` and starts a task to list the
* files in the folder and add them to `available`.
*/
private void listNextFolder(Queue<MailboxFolderId> queue,
Map<MailboxFolderId, Queue<MailboxFile>> available) {
synchronized (lock) {
if (state == State.DESTROYED) return;
MailboxFolderId folder = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallListFolder(folder, queue, available)));
}
}
private void apiCallListFolder(MailboxFolderId folder,
Queue<MailboxFolderId> queue,
Map<MailboxFolderId, Queue<MailboxFile>> available)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing folder");
try {
List<MailboxFile> files =
mailboxApi.getFiles(mailboxProperties, folder);
if (!files.isEmpty()) {
available.put(folder, new LinkedList<>(files));
}
} catch (TolerableFailureException e) {
LOG.warning("Folder does not exist");
}
if (queue.isEmpty()) {
LOG.info("Finished listing folders");
if (available.isEmpty()) onDownloadCycleFinished();
else createDownloadQueue(available);
} else {
listNextFolder(queue, available);
}
}
/**
* Visits the given folders in round-robin order to create a queue of up to
* {@link #MAX_ROUND_ROBIN_FILES} to download.
*/
private void createDownloadQueue(
Map<MailboxFolderId, Queue<MailboxFile>> available) {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
if (LOG.isLoggable(INFO)) {
LOG.info(available.size() + " folders have available files");
}
Queue<FolderFile> queue = createRoundRobinQueue(available);
if (LOG.isLoggable(INFO)) {
LOG.info("Downloading " + queue.size() + " files");
}
downloadNextFile(queue);
}
// Package access for testing
Queue<FolderFile> createRoundRobinQueue(
Map<MailboxFolderId, Queue<MailboxFile>> available) {
List<MailboxFolderId> roundRobin = new ArrayList<>(available.keySet());
// Shuffle the folders so we don't always favour the same folders
shuffle(roundRobin);
Queue<FolderFile> queue = new LinkedList<>();
while (queue.size() < MAX_ROUND_ROBIN_FILES && !available.isEmpty()) {
Iterator<MailboxFolderId> it = roundRobin.iterator();
while (queue.size() < MAX_ROUND_ROBIN_FILES && it.hasNext()) {
MailboxFolderId folder = it.next();
Queue<MailboxFile> files = available.get(folder);
MailboxFile file = files.remove();
queue.add(new FolderFile(folder, file.name));
if (files.isEmpty()) {
available.remove(folder);
it.remove();
}
}
}
return queue;
}
}

View File

@@ -1,17 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* An interface for converting an onion address to an HTTP URL, allowing the
* conversion to be customised for integration tests.
*/
@NotNullByDefault
interface UrlConverter {
/**
* Converts a raw onion address, excluding the .onion suffix, into an
* HTTP URL.
*/
String convertOnionToBaseUrl(String onion);
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.inject.Inject;
@NotNullByDefault
class UrlConverterImpl implements UrlConverter {
@Inject
UrlConverterImpl() {
}
@Override
public String convertOnionToBaseUrl(String onion) {
return "http://" + onion + ".onion";
}
}

View File

@@ -1,8 +1,10 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
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;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -11,8 +13,9 @@ import javax.annotation.concurrent.Immutable;
class BackoffFactoryImpl implements BackoffFactory { class BackoffFactoryImpl implements BackoffFactory {
@Override @Override
public Backoff createBackoff(int minInterval, int maxInterval, public Backoff createBackoff(EventBus eventBus, TransportId transportId,
double base) { int minInterval, int maxInterval, double base) {
return new BackoffImpl(minInterval, maxInterval, base); return new BackoffImpl(eventBus, transportId, minInterval, maxInterval,
base);
} }
} }

View File

@@ -1,7 +1,10 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
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.TransportId;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -11,11 +14,16 @@ import javax.annotation.concurrent.ThreadSafe;
@NotNullByDefault @NotNullByDefault
class BackoffImpl implements Backoff { class BackoffImpl implements Backoff {
private final EventBus eventBus;
private final TransportId transportId;
private final int minInterval, maxInterval; private final int minInterval, maxInterval;
private final double base; private final double base;
private final AtomicInteger backoff; private final AtomicInteger backoff;
BackoffImpl(int minInterval, int maxInterval, double base) { BackoffImpl(EventBus eventBus, TransportId transportId,
int minInterval, int maxInterval, double base) {
this.eventBus = eventBus;
this.transportId = transportId;
this.minInterval = minInterval; this.minInterval = minInterval;
this.maxInterval = maxInterval; this.maxInterval = maxInterval;
this.base = base; this.base = base;
@@ -37,6 +45,9 @@ class BackoffImpl implements Backoff {
@Override @Override
public void reset() { public void reset() {
backoff.set(0); int old = backoff.getAndSet(0);
if (old > 0) {
eventBus.broadcast(new PollingIntervalDecreasedEvent(transportId));
}
} }
} }

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
@@ -362,6 +363,11 @@ class PluginManagerImpl implements PluginManager, Service {
} }
} }
@Override
public void pollingIntervalDecreased() {
eventBus.broadcast(new PollingIntervalDecreasedEvent(id));
}
@Override @Override
public void handleConnection(DuplexTransportConnection d) { public void handleConnection(DuplexTransportConnection d) {
connectionManager.manageIncomingConnection(id, d); connectionManager.manageIncomingConnection(id, d);

View File

@@ -20,7 +20,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent; import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
@@ -107,10 +107,14 @@ class PollerImpl implements Poller, EventListener {
if (!c.isIncoming() && c.isException()) { if (!c.isIncoming() && c.isException()) {
connectToContact(c.getContactId(), c.getTransportId()); connectToContact(c.getContactId(), c.getTransportId());
} }
} else if (e instanceof ConnectionOpenedEvent) { } else if (e instanceof PollingIntervalDecreasedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e; PollingIntervalDecreasedEvent p = (PollingIntervalDecreasedEvent) e;
// Reschedule polling, the polling interval may have decreased TransportId t = p.getTransportId();
reschedule(c.getTransportId()); if (LOG.isLoggable(INFO)) {
LOG.info("Polling interval decreased for " + t);
}
// Reschedule polling
reschedule(t);
} else if (e instanceof TransportActiveEvent) { } else if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e; TransportActiveEvent t = (TransportActiveEvent) e;
// Poll the newly activated transport // Poll the newly activated transport
@@ -228,7 +232,7 @@ class PollerImpl implements Poller, EventListener {
if (!connected.contains(c)) if (!connected.contains(c))
properties.add(new Pair<>(e.getValue(), new Handler(c, t))); properties.add(new Pair<>(e.getValue(), new Handler(c, t)));
} }
if (!properties.isEmpty()) p.poll(properties); p.poll(properties);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -335,7 +335,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (getState() != ACTIVE) return; if (properties.isEmpty() || getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
@@ -427,13 +427,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
BdfList descriptor = new BdfList(); BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH); descriptor.add(TRANSPORT_ID_BLUETOOTH);
String address = getBluetoothAddress(); String address = getBluetoothAddress();
if (address != null) { if (address != null) descriptor.add(macToBytes(address));
try {
descriptor.add(macToBytes(address));
} catch (FormatException e) {
throw new RuntimeException();
}
}
return new BluetoothKeyAgreementListener(descriptor, ss); return new BluetoothKeyAgreementListener(descriptor, ss);
} }

View File

@@ -9,12 +9,14 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID; import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
@NotNullByDefault @NotNullByDefault
public class MailboxPluginFactory implements SimplexPluginFactory { public class MailboxPluginFactory implements SimplexPluginFactory {
private static final long MAX_LATENCY = DAYS.toMillis(14);
@Inject @Inject
MailboxPluginFactory() { MailboxPluginFactory() {
} }

View File

@@ -106,8 +106,7 @@ class RemovableDriveManagerImpl
@Override @Override
public boolean isWriterTaskNeeded(ContactId c) throws DbException { public boolean isWriterTaskNeeded(ContactId c) throws DbException {
return db.transactionWithResult(true, txn -> return db.transactionWithResult(true, txn ->
db.containsAcksToSend(txn, c) || db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
} }
@Override @Override

View File

@@ -285,7 +285,7 @@ class LanTcpPlugin extends TcpPlugin {
if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip)); if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip));
} }
return addrs; return addrs;
} catch (FormatException | UnknownHostException e) { } catch (IllegalArgumentException | UnknownHostException e) {
return emptyList(); return emptyList();
} }
} }

View File

@@ -56,8 +56,8 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, wakefulIoExecutor, LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, wakefulIoExecutor,
backoff, callback, MAX_LATENCY, MAX_IDLE_TIME, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME,
CONNECTION_TIMEOUT); CONNECTION_TIMEOUT);

View File

@@ -246,7 +246,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (getState() != ACTIVE) return; if (properties.isEmpty() || getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());

View File

@@ -60,8 +60,8 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
PortMapper portMapper = new PortMapperImpl(shutdownManager); PortMapper portMapper = new PortMapperImpl(shutdownManager);
WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, wakefulIoExecutor, WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, wakefulIoExecutor,
backoff, portMapper, callback, MAX_LATENCY, MAX_IDLE_TIME, backoff, portMapper, callback, MAX_LATENCY, MAX_IDLE_TIME,

View File

@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
@@ -26,6 +25,7 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -65,9 +65,9 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -94,7 +94,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES; import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -123,6 +125,45 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}"); private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
/**
* After this many consecutive successful connections to our own hidden
* service we consider the network to be stable.
* <p>
* This constant times {@link #POLLING_INTERVAL_UNSTABLE} should be
* greater than {@link #POLLING_INTERVAL_STABLE}. This ensures that if
* we experience a network outage that isn't detected by the
* {@link NetworkManager}, and if one of our contacts comes online during
* the outage, then either the outage lasts longer than
* {@link #POLLING_INTERVAL_STABLE}, in which case we detect the outage
* by failing to connect to our own hidden service, or the outage ends
* before the contact considers their own network connection to be stable,
* in which case the contact is still trying to connect to us when the
* outage ends. Either way, we don't end up in a situation where both we
* and the contact consider our network connections to be stable and stop
* trying to connect to each other, despite both being online.
*/
private static final int STABLE_NETWORK_THRESHOLD = 10;
/**
* After this many consecutive failed connections to our own hidden service
* we consider our connection to the Tor network to be broken.
*/
private static final int BROKEN_NETWORK_THRESHOLD = 3;
/**
* How often to poll our own hidden service when the network is considered
* to be stable.
*/
private static final int POLLING_INTERVAL_STABLE =
(int) MINUTES.toMillis(10);
/**
* How often to poll our own hidden service and our contacts' hidden
* services when the network is considered to be unstable.
*/
private static final int POLLING_INTERVAL_UNSTABLE =
(int) MINUTES.toMillis(2);
protected final Executor ioExecutor; protected final Executor ioExecutor;
private final Executor wakefulIoExecutor; private final Executor wakefulIoExecutor;
private final Executor connectionStatusExecutor; private final Executor connectionStatusExecutor;
@@ -131,7 +172,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final Clock clock; private final Clock clock;
private final BatteryManager batteryManager; private final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto; private final TorRendezvousCrypto torRendezvousCrypto;
private final PluginCallback callback; private final PluginCallback callback;
private final String architecture; private final String architecture;
@@ -151,6 +191,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile Socket controlSocket = null; private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null; private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null; private volatile Settings settings = null;
private volatile String ownOnion = null;
protected abstract int getProcessId(); protected abstract int getProcessId();
@@ -165,7 +206,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
ResourceProvider resourceProvider, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, PluginCallback callback,
String architecture, String architecture,
@@ -183,7 +223,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.resourceProvider = resourceProvider; this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider; this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto; this.torRendezvousCrypto = torRendezvousCrypto;
this.callback = callback; this.callback = callback;
this.architecture = architecture; this.architecture = architecture;
@@ -238,14 +277,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
// Load the settings // Load the settings
settings = callback.getSettings(); settings = callback.getSettings();
try { // Install or update the assets if necessary
// Install or update the assets if necessary if (!assetsAreUpToDate()) installAssets();
if (!assetsAreUpToDate()) installAssets();
// Start from the default config every time
extract(getConfigInputStream(), configFile);
} catch (IOException e) {
throw new PluginException(e);
}
if (cookieFile.exists() && !cookieFile.delete()) if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted"); LOG.warning("Old auth cookie not deleted");
// Start a new Tor process // Start a new Tor process
@@ -307,7 +340,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
info = controlConnection.getInfo("status/circuit-established"); info = controlConnection.getInfo("status/circuit-established");
if ("1".equals(info)) { if ("1".equals(info)) {
LOG.info("Tor has already built a circuit"); LOG.info("Tor has already built a circuit");
state.setCircuitBuilt(true); state.getAndSetCircuitBuilt(true);
} }
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -326,20 +359,25 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return doneFile.lastModified() > getLastUpdateTime(); return doneFile.lastModified() > getLastUpdateTime();
} }
private void installAssets() throws IOException { private void installAssets() throws PluginException {
// The done file may already exist from a previous installation try {
//noinspection ResultOfMethodCallIgnored // The done file may already exist from a previous installation
doneFile.delete(); //noinspection ResultOfMethodCallIgnored
// The GeoIP file may exist from a previous installation - we can doneFile.delete();
// save some space by deleting it. // The GeoIP file may exist from a previous installation - we can
// TODO: Remove after a reasonable migration period // save some space by deleting it.
// (added 2022-03-29) // TODO: Remove after a reasonable migration period
//noinspection ResultOfMethodCallIgnored // (added 2022-03-29)
geoIpFile.delete(); //noinspection ResultOfMethodCallIgnored
installTorExecutable(); geoIpFile.delete();
installObfs4Executable(); installTorExecutable();
if (!doneFile.createNewFile()) installObfs4Executable();
LOG.warning("Failed to create done file"); extract(getConfigInputStream(), configFile);
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
} catch (IOException e) {
throw new PluginException(e);
}
} }
protected void extract(InputStream in, File dest) throws IOException { protected void extract(InputStream in, File dest) throws IOException {
@@ -398,10 +436,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "SocksPort", torSocksPort); append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n"); strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n"); strb.append("GeoIPv6File\n");
append(strb, "ConnectionPadding", 0);
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
//noinspection CharsetObjectCanBeUsed //noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream( return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8"))); strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -478,7 +512,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.mergeSettings(s); callback.mergeSettings(s);
// Create a hidden service if necessary // Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort)); ioExecutor.execute(() -> publishHiddenService(localPort));
backoff.reset();
// Accept incoming hidden service connections from Tor // Accept incoming hidden service connections from Tor
acceptContactConnections(ss); acceptContactConnections(ss);
}); });
@@ -517,6 +550,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return; return;
} }
String onion3 = response.get(HS_ADDRESS); String onion3 = response.get(HS_ADDRESS);
ownOnion = onion3;
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("V3 hidden service " + scrubOnion(onion3)); LOG.info("V3 hidden service " + scrubOnion(onion3));
} }
@@ -545,13 +579,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return; return;
} }
LOG.info("Connection received"); LOG.info("Connection received");
backoff.reset();
callback.handleConnection(new TorTransportConnection(this, s)); callback.handleConnection(new TorTransportConnection(this, s));
} }
} }
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!state.enableNetwork(enable)) return; // Unchanged state.enableNetwork(enable);
try { try {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -559,20 +592,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void enableBridges(List<BridgeType> bridgeTypes) private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
throws IOException { throws IOException {
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
try { try {
if (bridgeTypes.isEmpty()) { if (enable) {
controlConnection.setConf("UseBridges", "0");
controlConnection.resetConf(singletonList("Bridge"));
} else {
Collection<String> conf = new ArrayList<>(); Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1"); conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (bridgeTypes.contains(MEEK)) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
}
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
for (BridgeType bridgeType : bridgeTypes) { for (BridgeType bridgeType : bridgeTypes) {
conf.addAll(circumventionProvider.getBridges(bridgeType)); conf.addAll(circumventionProvider.getBridges(bridgeType));
} }
controlConnection.setConf(conf); controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
} }
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -586,6 +627,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (controlSocket != null && controlConnection != null) { if (controlSocket != null && controlConnection != null) {
try { try {
LOG.info("Stopping Tor"); LOG.info("Stopping Tor");
controlConnection.setConf("DisableNetwork", "1");
controlConnection.shutdownTor("TERM"); controlConnection.shutdownTor("TERM");
controlSocket.close(); controlSocket.close();
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -613,14 +655,58 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public int getPollingInterval() { public int getPollingInterval() {
return backoff.getPollingInterval(); if (state.isNetworkStable()) {
LOG.info("Using stable polling interval");
return POLLING_INTERVAL_STABLE;
} else {
LOG.info("Using unstable polling interval");
return POLLING_INTERVAL_UNSTABLE;
}
} }
@Override @Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>> public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) { properties) {
if (getState() != ACTIVE) return; if (getState() != ACTIVE) return;
backoff.increment(); String ownOnion = this.ownOnion;
if (ownOnion == null) {
// Our own hidden service hasn't been created yet
pollContacts(properties);
} else {
// If the network is unstable, poll our contacts
boolean stable = state.isNetworkStable();
if (!stable) pollContacts(properties);
// Poll our own hidden service to check if the network is stable
wakefulIoExecutor.execute(() -> {
LOG.info("Connecting to own hidden service");
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V3, ownOnion);
DuplexTransportConnection d = createConnection(p);
if (d == null) {
LOG.info("Could not connect to own hidden service");
state.onStabilityCheckFailed();
// If the network was previously considered stable then
// we didn't poll our contacts above, so poll them now
if (stable) pollContacts(properties);
} else {
LOG.info("Connected to own hidden service");
// Close the connection (this will cause the other end of
// the connection to log an EOFException)
try {
d.getWriter().dispose(false);
d.getReader().dispose(false, false);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
state.onStabilityCheckSucceeded();
}
});
}
}
private void pollContacts(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
if (properties.isEmpty() || getState() != ACTIVE) return;
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond()); connect(p.getFirst(), p.getSecond());
} }
@@ -629,10 +715,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void connect(TransportProperties p, ConnectionHandler h) { private void connect(TransportProperties p, ConnectionHandler h) {
wakefulIoExecutor.execute(() -> { wakefulIoExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p); DuplexTransportConnection d = createConnection(p);
if (d != null) { if (d != null) h.handleConnection(d);
backoff.reset();
h.handleConnection(d);
}
}); });
} }
@@ -753,9 +836,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void circuitStatus(String status, String id, String path) { public void circuitStatus(String status, String id, String path) {
// In case of races between receiving CIRCUIT_ESTABLISHED and setting // In case of races between receiving CIRCUIT_ESTABLISHED and setting
// DisableNetwork, set our circuitBuilt flag if not already set // DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && state.setCircuitBuilt(true)) { if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built"); LOG.info("Circuit built");
backoff.reset();
} }
} }
@@ -782,6 +864,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void message(String severity, String msg) { public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg); if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (msg.startsWith("Switching to guard context")) {
state.resetNetworkStability();
}
} }
@Override @Override
@@ -800,13 +885,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
@Override
public void controlConnectionClosed() {
if (state.isTorRunning()) {
throw new RuntimeException("Control connection closed");
}
}
private String removeSeverity(String msg) { private String removeSeverity(String msg) {
return msg.replaceFirst("[^ ]+ ", ""); return msg.replaceFirst("[^ ]+ ", "");
} }
@@ -815,14 +893,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) { if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
LOG.info("Bootstrapped"); LOG.info("Bootstrapped");
state.setBootstrapped(); state.setBootstrapped();
backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (state.setCircuitBuilt(true)) { if (!state.getAndSetCircuitBuilt(true)) LOG.info("Circuit built");
LOG.info("Circuit built");
backoff.reset();
}
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.setCircuitBuilt(false)) { if (state.getAndSetCircuitBuilt(false)) {
LOG.info("Circuit not built"); LOG.info("Circuit not built");
// TODO: Disable and re-enable network to prompt Tor to rebuild // TODO: Disable and re-enable network to prompt Tor to rebuild
// its guard/bridge connections? This will also close any // its guard/bridge connections? This will also close any
@@ -865,7 +939,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) { if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e;
if (c.getTransportId().equals(getId())
&& !c.isIncoming() && c.isException()) {
LOG.info("Outgoing connection closed with exception");
// The failure may indicate that the network is unstable
state.resetNetworkStability();
}
} else if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString())) { if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated"); LOG.info("Tor settings updated");
@@ -882,6 +964,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void disableAndReenableNetwork() {
connectionStatusExecutor.execute(() -> {
try {
if (state.isTorRunning()) enableNetwork(false);
} catch (IOException ex) {
logException(LOG, WARNING, ex);
}
});
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
}
private void updateConnectionStatus(NetworkStatus status, private void updateConnectionStatus(NetworkStatus status,
boolean charging) { boolean charging) {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
@@ -913,8 +1007,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
int reasonsDisabled = 0; int reasonsDisabled = 0;
boolean enableNetwork = false, enableConnectionPadding = false; boolean enableNetwork = false, enableBridges = false;
List<BridgeType> bridgeTypes = emptyList(); boolean enableConnectionPadding = false;
List<BridgeType> bridgeTypes =
circumventionProvider.getSuitableBridgeTypes(country);
if (!online) { if (!online) {
LOG.info("Disabling network, device is offline"); LOG.info("Disabling network, device is offline");
@@ -943,12 +1039,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true; enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES || if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (ipv6Only) { if (ipv6Only) bridgeTypes = singletonList(MEEK);
bridgeTypes = singletonList(MEEK); enableBridges = true;
} else {
bridgeTypes = circumventionProvider
.getSuitableBridgeTypes(country);
}
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Using bridge types " + bridgeTypes); LOG.info("Using bridge types " + bridgeTypes);
} }
@@ -968,9 +1060,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try { try {
if (enableNetwork) { if (enableNetwork) {
enableBridges(bridgeTypes); enableBridges(enableBridges, bridgeTypes);
enableConnectionPadding(enableConnectionPadding); enableConnectionPadding(enableConnectionPadding);
enableIpv6(ipv6Only); useIpv6(ipv6Only);
} }
enableNetwork(enableNetwork); enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
@@ -980,7 +1072,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void enableConnectionPadding(boolean enable) throws IOException { private void enableConnectionPadding(boolean enable) throws IOException {
if (!state.enableConnectionPadding(enable)) return; // Unchanged
try { try {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -988,11 +1079,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void enableIpv6(boolean enable) throws IOException { private void useIpv6(boolean ipv6Only) throws IOException {
if (!state.enableIpv6(enable)) return; // Unchanged
try { try {
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1"); controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0"); controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -1007,8 +1097,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
stopped = false, stopped = false,
networkInitialised = false, networkInitialised = false,
networkEnabled = false, networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
bootstrapped = false, bootstrapped = false,
circuitBuilt = false, circuitBuilt = false,
settingsChecked = false; settingsChecked = false;
@@ -1016,6 +1104,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this") @GuardedBy("this")
private int reasonsDisabled = 0; private int reasonsDisabled = 0;
/**
* The number of consecutive successful (positive) or failed (negative)
* connections to our own hidden service.
*/
@GuardedBy("this")
private int networkStability = 0;
@GuardedBy("this") @GuardedBy("this")
@Nullable @Nullable
private ServerSocket serverSocket = null; private ServerSocket serverSocket = null;
@@ -1023,9 +1118,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this") @GuardedBy("this")
private int orConnectionsConnected = 0; private int orConnectionsConnected = 0;
@GuardedBy("this")
private List<BridgeType> bridgeTypes = emptyList();
private synchronized void setStarted() { private synchronized void setStarted() {
started = true; started = true;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
@@ -1046,66 +1138,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private synchronized void setBootstrapped() { private synchronized void setBootstrapped() {
boolean wasBootstrapped = bootstrapped;
bootstrapped = true; bootstrapped = true;
if (!wasBootstrapped) callback.pluginStateChanged(getState());
}
/**
* Sets the `circuitBuilt` flag and returns true if the flag has
* changed.
*/
private synchronized boolean setCircuitBuilt(boolean built) {
if (built == circuitBuilt) return false; // Unchanged
circuitBuilt = built;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
return true; // Changed
} }
/** private synchronized boolean getAndSetCircuitBuilt(boolean built) {
* Sets the `networkEnabled` flag and returns true if the flag has boolean old = circuitBuilt;
* changed. circuitBuilt = built;
*/ if (built != old) callback.pluginStateChanged(getState());
private synchronized boolean enableNetwork(boolean enable) { return old;
boolean wasInitialised = networkInitialised; }
boolean wasEnabled = networkEnabled;
private synchronized void enableNetwork(boolean enable) {
networkInitialised = true; networkInitialised = true;
networkEnabled = enable; networkEnabled = enable;
if (!enable) circuitBuilt = false; if (!enable) circuitBuilt = false;
if (!wasInitialised || enable != wasEnabled) { callback.pluginStateChanged(getState());
callback.pluginStateChanged(getState());
}
return enable != wasEnabled;
} }
/** private synchronized void setReasonsDisabled(int reasonsDisabled) {
* Sets the `paddingEnabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableConnectionPadding(boolean enable) {
if (enable == paddingEnabled) return false; // Unchanged
paddingEnabled = enable;
return true; // Changed
}
/**
* Sets the `ipv6Enabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableIpv6(boolean enable) {
if (enable == ipv6Enabled) return false; // Unchanged
ipv6Enabled = enable;
return true; // Changed
}
private synchronized void setReasonsDisabled(int reasons) {
boolean wasChecked = settingsChecked;
settingsChecked = true; settingsChecked = true;
int oldReasons = reasonsDisabled; this.reasonsDisabled = reasonsDisabled;
reasonsDisabled = reasons; callback.pluginStateChanged(getState());
if (!wasChecked || reasons != oldReasons) {
callback.pluginStateChanged(getState());
}
} }
// Doesn't affect getState() // Doesn't affect getState()
@@ -1120,17 +1174,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (serverSocket == ss) serverSocket = null; if (serverSocket == ss) serverSocket = null;
} }
/**
* Sets the list of bridge types being used and returns true if the
* list has changed. The list is empty if bridges are disabled.
* Doesn't affect getState().
*/
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
if (types.equals(bridgeTypes)) return false; // Unchanged
bridgeTypes = types;
return true; // Changed
}
private synchronized State getState() { private synchronized State getState() {
if (!started || stopped || !settingsChecked) { if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING; return STARTING_STOPPING;
@@ -1162,6 +1205,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
logOrConnections(); logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) { if (orConnectionsConnected == 0 && oldConnected != 0) {
resetNetworkStability();
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
} }
} }
@@ -1172,5 +1216,43 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info(orConnectionsConnected + " OR connections connected"); LOG.info(orConnectionsConnected + " OR connections connected");
} }
} }
private synchronized boolean isNetworkStable() {
return networkStability >= STABLE_NETWORK_THRESHOLD;
}
private synchronized void onStabilityCheckSucceeded() {
if (networkStability <= 0) networkStability = 1;
else networkStability++;
logNetworkStability();
}
private synchronized void onStabilityCheckFailed() {
if (networkStability >= 0) networkStability = -1;
else networkStability--;
if (networkStability <= -BROKEN_NETWORK_THRESHOLD) {
LOG.warning("Connection to Tor appears to be broken");
resetNetworkStability();
disableAndReenableNetwork();
} else {
logNetworkStability();
}
}
private synchronized void resetNetworkStability() {
int old = networkStability;
networkStability = 0;
logNetworkStability();
if (old >= STABLE_NETWORK_THRESHOLD) {
callback.pollingIntervalDecreased();
}
}
@GuardedBy("this")
private void logNetworkStability() {
if (LOG.isLoggable(INFO)) {
LOG.info("Network stability score " + networkStability);
}
}
} }
} }

View File

@@ -6,8 +6,6 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
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.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TorControlPort; import org.briarproject.bramble.api.plugin.TorControlPort;
@@ -41,16 +39,12 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
protected static final int MAX_LATENCY = 30 * 1000; // 30 seconds protected static final int MAX_LATENCY = 30 * 1000; // 30 seconds
protected static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds protected 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;
protected final Executor ioExecutor, wakefulIoExecutor; protected final Executor ioExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager; protected final NetworkManager networkManager;
protected final LocationUtils locationUtils; protected final LocationUtils locationUtils;
protected final EventBus eventBus; protected final EventBus eventBus;
protected final SocketFactory torSocketFactory; protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory;
protected final ResourceProvider resourceProvider; protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider; protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager; protected final BatteryManager batteryManager;
@@ -66,7 +60,6 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
LocationUtils locationUtils, LocationUtils locationUtils,
EventBus eventBus, EventBus eventBus,
SocketFactory torSocketFactory, SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
@@ -81,7 +74,6 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.eventBus = eventBus; this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider; this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider; this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
@@ -95,7 +87,7 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
@Nullable @Nullable
abstract String getArchitectureForTorBinary(); abstract String getArchitectureForTorBinary();
abstract TorPlugin createPluginInstance(Backoff backoff, abstract TorPlugin createPluginInstance(
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture); String architecture);
@@ -122,11 +114,9 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
LOG.info("The selected architecture for Tor is " + architecture); LOG.info("The selected architecture for Tor is " + architecture);
} }
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto); new TorRendezvousCryptoImpl(crypto);
TorPlugin plugin = createPluginInstance(backoff, torRendezvousCrypto, TorPlugin plugin = createPluginInstance(torRendezvousCrypto,
callback, architecture); callback, architecture);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;

View File

@@ -44,14 +44,12 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES; import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS; import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
@@ -234,19 +232,11 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof MessageSharedEvent) { } else if (e instanceof MessageSharedEvent) {
MessageSharedEvent m = (MessageSharedEvent) e; generateOffer();
// If the contact is present in the map (ie the value is not null)
// and the value is true, the message's group is shared with the
// contact and therefore the message may now be sendable
if (m.getGroupVisibility().get(contactId) == TRUE) {
generateOffer();
}
} else if (e instanceof GroupVisibilityUpdatedEvent) { } else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e; GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED && if (g.getAffectedContacts().contains(contactId))
g.getAffectedContacts().contains(contactId)) {
generateOffer(); generateOffer();
}
} else if (e instanceof MessageRequestedEvent) { } else if (e instanceof MessageRequestedEvent) {
if (((MessageRequestedEvent) e).getContactId().equals(contactId)) if (((MessageRequestedEvent) e).getContactId().equals(contactId))
generateBatch(); generateBatch();
@@ -320,8 +310,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Collection<Message> batch = Collection<Message> batch =
db.generateRequestedBatch(txn, contactId, db.generateRequestedBatch(txn, contactId,
BATCH_CAPACITY, maxLatency); BATCH_CAPACITY, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId, setNextSendTime(db.getNextSendTime(txn, contactId));
maxLatency));
return batch; return batch;
}); });
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -364,8 +353,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Offer o = db.transactionWithNullableResult(false, txn -> { Offer o = db.transactionWithNullableResult(false, txn -> {
Offer offer = db.generateOffer(txn, contactId, Offer offer = db.generateOffer(txn, contactId,
MAX_MESSAGE_IDS, maxLatency); MAX_MESSAGE_IDS, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId, setNextSendTime(db.getNextSendTime(txn, contactId));
maxLatency));
return offer; return offer;
}); });
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))

View File

@@ -7,16 +7,14 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.DeferredSendHandler;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -31,7 +29,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
/** /**
* A {@link SimplexOutgoingSession} for sending and acking messages via a * A {@link SimplexOutgoingSession} for sending and acking messages via a
* mailbox. The session uses a {@link OutgoingSessionRecord} to record the IDs * mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
* of the messages sent and acked during the session so that they can be * of the messages sent and acked during the session so that they can be
* recorded in the DB as sent or acked after the file has been successfully * recorded in the DB as sent or acked after the file has been successfully
* uploaded to the mailbox. * uploaded to the mailbox.
@@ -43,7 +41,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
private static final Logger LOG = private static final Logger LOG =
getLogger(MailboxOutgoingSession.class.getName()); getLogger(MailboxOutgoingSession.class.getName());
private final OutgoingSessionRecord sessionRecord; private final DeferredSendHandler deferredSendHandler;
private final long initialCapacity; private final long initialCapacity;
MailboxOutgoingSession(DatabaseComponent db, MailboxOutgoingSession(DatabaseComponent db,
@@ -53,42 +51,36 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
long maxLatency, long maxLatency,
StreamWriter streamWriter, StreamWriter streamWriter,
SyncRecordWriter recordWriter, SyncRecordWriter recordWriter,
OutgoingSessionRecord sessionRecord, DeferredSendHandler deferredSendHandler,
long capacity) { long capacity) {
super(db, eventBus, contactId, transportId, maxLatency, streamWriter, super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
recordWriter); recordWriter);
this.sessionRecord = sessionRecord; this.deferredSendHandler = deferredSendHandler;
this.initialCapacity = capacity; this.initialCapacity = capacity;
} }
@Override @Override
void sendAcks() throws DbException, IOException { void sendAcks() throws DbException, IOException {
List<MessageId> idsToAck = loadMessageIdsToAck(); while (!isInterrupted()) {
int idsSent = 0; Collection<MessageId> idsToAck = loadMessageIdsToAck();
while (idsSent < idsToAck.size() && !isInterrupted()) { if (idsToAck.isEmpty()) break;
int idsRemaining = idsToAck.size() - idsSent; recordWriter.writeAck(new Ack(idsToAck));
long capacity = getRemainingCapacity(); deferredSendHandler.onAckSent(idsToAck);
long idCapacity =
(capacity - RECORD_HEADER_BYTES) / MessageId.LENGTH;
if (idCapacity == 0) break; // Out of capacity
int idsInRecord = (int) min(idCapacity, MAX_MESSAGE_IDS);
int idsToSend = min(idsRemaining, idsInRecord);
List<MessageId> acked =
idsToAck.subList(idsSent, idsSent + idsToSend);
recordWriter.writeAck(new Ack(acked));
sessionRecord.onAckSent(acked);
LOG.info("Sent ack"); LOG.info("Sent ack");
idsSent += idsToSend;
} }
} }
private List<MessageId> loadMessageIdsToAck() throws DbException { private Collection<MessageId> loadMessageIdsToAck() throws DbException {
long idCapacity = (getRemainingCapacity() - RECORD_HEADER_BYTES)
/ MessageId.LENGTH;
if (idCapacity <= 0) return emptyList(); // Out of capacity
int maxMessageIds = (int) min(idCapacity, MAX_MESSAGE_IDS);
Collection<MessageId> ids = db.transactionWithResult(true, txn -> Collection<MessageId> ids = db.transactionWithResult(true, txn ->
db.getMessagesToAck(txn, contactId)); db.getMessagesToAck(txn, contactId, maxMessageIds));
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(ids.size() + " messages to ack"); LOG.info(ids.size() + " messages to ack");
} }
return new ArrayList<>(ids); return ids;
} }
private long getRemainingCapacity() { private long getRemainingCapacity() {
@@ -104,7 +96,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
db.getMessageToSend(txn, contactId, m, maxLatency, false)); db.getMessageToSend(txn, contactId, m, maxLatency, false));
if (message == null) continue; // No longer shared if (message == null) continue; // No longer shared
recordWriter.writeMessage(message); recordWriter.writeMessage(message);
sessionRecord.onMessageSent(m); deferredSendHandler.onMessageSent(m);
LOG.info("Sent message"); LOG.info("Sent message");
} }
} }

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.Priority; import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.PriorityHandler; import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncRecordReader; import org.briarproject.bramble.api.sync.SyncRecordReader;
@@ -26,8 +25,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_FILE_PAYLOAD_BYTES;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class SyncSessionFactoryImpl implements SyncSessionFactory { class SyncSessionFactoryImpl implements SyncSessionFactory {
@@ -76,18 +73,6 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
} }
} }
@Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, StreamWriter streamWriter,
OutgoingSessionRecord sessionRecord) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new MailboxOutgoingSession(db, eventBus, c, t, maxLatency,
streamWriter, recordWriter, sessionRecord,
MAX_FILE_PAYLOAD_BYTES);
}
@Override @Override
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, int maxIdleTime, StreamWriter streamWriter, long maxLatency, int maxIdleTime, StreamWriter streamWriter,

View File

@@ -13,24 +13,21 @@ d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=T
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0 d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0 n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0 n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0 n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0 n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 83.97.179.29:1199 D83068BFAA28E71DB024B786E1E803BE14257127 cert=IduGtt05tM59Xmvo0oVNWgIRgY4OGPJjFP+y2oa6RMDHQBL/GRyFOOgX70iiazNAIJNkPw iat-mode=0
n Bridge obfs4 207.181.244.13:40132 37FE8D782F5DD2BAEEDAAB8257B701344676B6DD cert=f5Hbfn3ToMzH170cV8DfLly3vRynveidfOfDcbradIDtbLDX15V2yQ8eEH2CPKQJmQR2Hg iat-mode=0
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0 n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0 n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0 n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 176.123.2.253:1933 B855D141CE6C4DE0F7EA4AAED83EBA8373FA8191 cert=1rOoSaRagc6PPR//paIl+ukv1N+xWKCdBXMFxK0k/moEwH0lk5bURBrUDzIX35fVzaiicQ iat-mode=0
n Bridge obfs4 5.252.176.61:9418 3E61130100AD827AB9CB33DAC052D9BC49A39509 cert=/aMyBPnKbQFISithD3i1KHUdpWeMpWG3SvUpq1YWCf2EQohFxQfw+646gW1knm4BI/DLRA iat-mode=0 n Bridge obfs4 5.252.176.61:9418 3E61130100AD827AB9CB33DAC052D9BC49A39509 cert=/aMyBPnKbQFISithD3i1KHUdpWeMpWG3SvUpq1YWCf2EQohFxQfw+646gW1knm4BI/DLRA iat-mode=0
n Bridge obfs4 70.34.213.156:12345 BC1C79ABBAE085D305346E7A2B0E838953B4B4D3 cert=3Sk4uA3/NiAsn4ObOUzjIzARclGmkiEUrku8o8bkq4ZL+dek9uLj/d5LZ5nAXT6L9S0CZA iat-mode=0
n Bridge obfs4 202.61.224.111:6902 A4F91299763DB925AE3BD29A0FC1A9821E5D9BAE cert=NBKm2MJ83wMvYShkqpD5RrbDtW5YpIZrFNnMw7Dj1XOM3plU60Bh4eziaQXe8fGtb8ZqKg iat-mode=0 n Bridge obfs4 202.61.224.111:6902 A4F91299763DB925AE3BD29A0FC1A9821E5D9BAE cert=NBKm2MJ83wMvYShkqpD5RrbDtW5YpIZrFNnMw7Dj1XOM3plU60Bh4eziaQXe8fGtb8ZqKg iat-mode=0
n Bridge obfs4 87.121.72.109:9002 C8081D4731C953FA4AE166946E72B29153351E34 cert=bikAqxKV6Ch5gFCBTdPI28VeShYa1ZgkLmvc7YZNLWFsFZoaCULL/3AQKjpIfvSiJs5jGQ iat-mode=0 v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
n Bridge obfs4 172.104.17.96:17900 B6B37AC96E163D0A5ECE55826D17B50B70F0A7F8 cert=gUz7svhPxoALgeN4lMYrXK7NBnaDqwu6SKRJOhaO9IIMBpnB8UhMCMKzzMho3b0RxWzBVg iat-mode=0
n Bridge obfs4 70.34.249.113:443 F441B16ABB1055794C2CE01821FC05047B2C8CFC cert=MauLNoyq8EwjY4Qe0oASYzs2hXdSjNgy+BtP9oo1naHhRsyKTtAZzeNv08RnzWjMJrTwcg iat-mode=0
n Bridge obfs4 85.212.104.44:27623 9000BDF7063F808E554C320545BCF264222CDFE9 cert=eKSXeJLnFv6RDAap5vM2sqsi8GIWhlMi5prs8hu0HTKWTH9KjZhhoj3Cg/gIuoL8n8TXGQ iat-mode=0
n Bridge obfs4 104.168.68.90:443 ED55B3C321E44EA7E50EF568C8A63CF75E89A58C cert=fgonxDvltTp8nmcOE9sUG94eOAALxETVVXAwnTZJLPpf7rjPuTp+abKl4VyFkxfcLRr5KQ iat-mode=0
n Bridge obfs4 158.247.207.151:443 6170ADBBB6C1859A8E7E4416BB8AB3AF471AE47F cert=Od4izlwLnXcq7LMSOJtnZLtklaUn+X+gPcBwN7RUEkk9rqxRRYNHW7as8g6+jheDsazxAQ iat-mode=0
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
n Bridge obfs4 85.214.28.204:47111 78A36E46BB082A471848239D3F4390A8F8C6084D cert=96sr3eaUFBDu4wFVAQIfNFElh0UNuutJ/3/Fh2Vu2PHfacQ8bwfY02bwG351U8BZpLnfUQ iat-mode=0
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31 v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
v Bridge 185.189.195.124:8199 A1F3EE78F9C2343668E68FEB84358A4C742831A5 m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
v Bridge 135.23.182.26:11393 6E9A100D1E0570F0012735D64EEB4CB62AC258D9
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.connection; package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection; import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
@@ -58,10 +57,6 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
private final Priority high = private final Priority high =
new Priority(fromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")); new Priority(fromHexString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"));
public ConnectionRegistryImplTest() throws FormatException {
// required for throws declaration
}
@Test @Test
public void testRegisterMultipleConnections() { public void testRegisterMultipleConnections() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{

View File

@@ -10,8 +10,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
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.test.BrambleCoreIntegrationTestModule; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
import org.briarproject.bramble.test.TestDnsModule;
import org.briarproject.bramble.test.TestSocksModule;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -22,9 +20,7 @@ import dagger.Component;
@Singleton @Singleton
@Component(modules = { @Component(modules = {
BrambleCoreIntegrationTestModule.class, BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class, BrambleCoreModule.class
TestDnsModule.class,
TestSocksModule.class
}) })
interface ContactExchangeIntegrationTestComponent interface ContactExchangeIntegrationTestComponent
extends BrambleCoreIntegrationTestEagerSingletons { extends BrambleCoreIntegrationTestEagerSingletons {

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.Collection; import java.util.Collection;
@@ -62,9 +63,13 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final long timestamp = System.currentTimeMillis(); private final long timestamp = System.currentTimeMillis();
private final boolean alice = new Random().nextBoolean(); private final boolean alice = new Random().nextBoolean();
private final ContactManagerImpl contactManager = private ContactManagerImpl contactManager;
new ContactManagerImpl(db, keyManager, identityManager,
pendingContactFactory); @Before
public void setUp() {
contactManager = new ContactManagerImpl(db, keyManager,
identityManager, pendingContactFactory);
}
@Test @Test
public void testAddContact() throws Exception { public void testAddContact() throws Exception {

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.digests.Blake2bDigest; import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.junit.Test; import org.junit.Test;
@@ -48,7 +47,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
}; };
@Test @Test
public void testDigestWithKeyedTestVectors() throws FormatException { public void testDigestWithKeyedTestVectors() {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) { for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]); byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
byte[] key = StringUtils.fromHexString(keyedTestVector[1]); byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
@@ -64,8 +63,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
} }
@Test @Test
public void testDigestWithKeyedTestVectorsAndRandomUpdate() public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
throws FormatException {
Random random = new Random(); Random random = new Random();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) { for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
@@ -140,7 +138,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
} }
@Test @Test
public void runSelfTest() throws FormatException { public void runSelfTest() {
Blake2bDigest testDigest = new Blake2bDigest(256); Blake2bDigest testDigest = new Blake2bDigest(256);
byte[] md = new byte[64]; byte[] md = new byte[64];

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
@@ -84,7 +83,7 @@ public class KeyAgreementTest extends BrambleTestCase {
} }
@Test @Test
public void testRfc7748TestVector() throws FormatException { public void testRfc7748TestVector() {
byte[] aPriv = parsePrivateKey(ALICE_PRIVATE); byte[] aPriv = parsePrivateKey(ALICE_PRIVATE);
byte[] aPub = fromHexString(ALICE_PUBLIC); byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = parsePrivateKey(BOB_PRIVATE); byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
@@ -98,8 +97,7 @@ public class KeyAgreementTest extends BrambleTestCase {
} }
@Test @Test
public void testDerivesSameSharedSecretFromEquivalentPublicKey() public void testDerivesSameSharedSecretFromEquivalentPublicKey() {
throws FormatException {
byte[] aPub = fromHexString(ALICE_PUBLIC); byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = parsePrivateKey(BOB_PRIVATE); byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
byte[] sharedSecret = fromHexString(SHARED_SECRET); byte[] sharedSecret = fromHexString(SHARED_SECRET);
@@ -170,7 +168,7 @@ public class KeyAgreementTest extends BrambleTestCase {
return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs); return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs);
} }
private byte[] parsePrivateKey(String hex) throws FormatException { private byte[] parsePrivateKey(String hex) {
// Private keys need to be clamped because curve25519-java does the // Private keys need to be clamped because curve25519-java does the
// clamping at key generation time, not multiplication time // clamping at key generation time, not multiplication time
return AgreementKeyParser.clamp(fromHexString(hex)); return AgreementKeyParser.clamp(fromHexString(hex));

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -16,11 +15,11 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
// Test vectors from the NaCl paper // Test vectors from the NaCl paper
// http://cr.yp.to/highspeed/naclcrypto-20090310.pdf // http://cr.yp.to/highspeed/naclcrypto-20090310.pdf
private final byte[] TEST_KEY = StringUtils.fromHexString( private static final byte[] TEST_KEY = StringUtils.fromHexString(
"1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389"); "1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389");
private final byte[] TEST_IV = StringUtils.fromHexString( private static final byte[] TEST_IV = StringUtils.fromHexString(
"69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37"); "69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37");
private final byte[] TEST_PLAINTEXT = StringUtils.fromHexString( private static final byte[] TEST_PLAINTEXT = StringUtils.fromHexString(
"be075fc53c81f2d5cf141316" + "be075fc53c81f2d5cf141316" +
"ebeb0c7b5228c52a4c62cbd4" + "ebeb0c7b5228c52a4c62cbd4" +
"4b66849b64244ffce5ecbaaf" + "4b66849b64244ffce5ecbaaf" +
@@ -32,7 +31,7 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
"048977eb48f59ffd4924ca1c" + "048977eb48f59ffd4924ca1c" +
"60902e52f0a089bc76897040" + "60902e52f0a089bc76897040" +
"e082f937763848645e0705"); "e082f937763848645e0705");
private final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString( private static final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString(
"f3ffc7703f9400e52a7dfb4b" + "f3ffc7703f9400e52a7dfb4b" +
"3d3305d98e993b9f48681273" + "3d3305d98e993b9f48681273" +
"c29650ba32fc76ce48332ea7" + "c29650ba32fc76ce48332ea7" +
@@ -47,10 +46,6 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
"a43d14a6599b1f654cb45a74" + "a43d14a6599b1f654cb45a74" +
"e355a5"); "e355a5");
public XSalsa20Poly1305AuthenticatedCipherTest() throws FormatException {
// required for throws declaration
}
@Test @Test
public void testEncrypt() throws Exception { public void testEncrypt() throws Exception {
SecretKey k = new SecretKey(TEST_KEY); SecretKey k = new SecretKey(TEST_KEY);

View File

@@ -618,12 +618,11 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readDictionary(); r.readDictionary();
} }
private void setContents(String hex) throws FormatException { private void setContents(String hex) {
setContents(hex, DEFAULT_MAX_BUFFER_SIZE); setContents(hex, DEFAULT_MAX_BUFFER_SIZE);
} }
private void setContents(String hex, int maxBufferSize) private void setContents(String hex, int maxBufferSize) {
throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex)); ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex));
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize); r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.data;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -16,8 +17,14 @@ import static org.junit.Assert.assertArrayEquals;
public class BdfWriterImplTest extends BrambleTestCase { public class BdfWriterImplTest extends BrambleTestCase {
private final ByteArrayOutputStream out = new ByteArrayOutputStream(); private ByteArrayOutputStream out = null;
private final BdfWriterImpl w = new BdfWriterImpl(out); private BdfWriterImpl w = null;
@Before
public void setUp() {
out = new ByteArrayOutputStream();
w = new BdfWriterImpl(out);
}
@Test @Test
public void testWriteNull() throws IOException { public void testWriteNull() throws IOException {

View File

@@ -70,7 +70,6 @@ import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED; import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES; import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
@@ -284,16 +283,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
temporary, null); temporary, null);
oneOf(database).mergeMessageMetadata(txn, messageId, metadata); oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// Broadcast events for message being added and changing state // The message was added, so the listeners should be called
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(
MessageStateChangedEvent.class))); MessageStateChangedEvent.class)));
// If message is shared, get group visibility and broadcast event if (shared)
if (shared) {
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(singletonMap(contactId, true)));
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
}
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
@@ -308,11 +303,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(27).of(database).startTransaction(); exactly(25).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(27).of(database).containsContact(txn, contactId); exactly(25).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
exactly(27).of(database).abortTransaction(txn); exactly(25).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
@@ -326,23 +321,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected // Expected
} }
try {
db.transaction(true, transaction ->
db.containsAcksToSend(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.containsMessagesToSend(transaction, contactId,
123, true));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.generateAck(transaction, contactId, 123)); db.generateAck(transaction, contactId, 123));
@@ -394,7 +372,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
try { try {
db.transaction(true, transaction -> db.transaction(true, transaction ->
db.getMessagesToAck(transaction, contactId)); db.getMessagesToAck(transaction, contactId, 123));
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
@@ -699,11 +677,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the message is in the DB (which it's not) // Check whether the message is in the DB (which it's not)
exactly(16).of(database).startTransaction(); exactly(15).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(16).of(database).containsMessage(txn, messageId); exactly(15).of(database).containsMessage(txn, messageId);
will(returnValue(false)); will(returnValue(false));
exactly(16).of(database).abortTransaction(txn); exactly(15).of(database).abortTransaction(txn);
// Allow other checks to pass // Allow other checks to pass
allowing(database).containsContact(txn, contactId); allowing(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
@@ -727,14 +705,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected // Expected
} }
try {
db.transaction(true, transaction ->
db.getGroupId(transaction, messageId));
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try { try {
db.transaction(true, transaction -> db.transaction(true, transaction ->
db.getMessage(transaction, messageId)); db.getMessage(transaction, messageId));
@@ -1409,7 +1379,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).lowerAckFlag(txn, contactId, acked); // First message is still visible to the contact - flag lowered
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
// Second message is no longer visible - flag not lowered
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
will(returnValue(false));
oneOf(database)
.lowerAckFlag(txn, contactId, singletonList(messageId));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
@@ -1886,16 +1863,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getMessageDependents(txn, messageId); oneOf(database).getMessageDependents(txn, messageId);
// Broadcast events for message being added and changing state // broadcast for message added event
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(
MessageStateChangedEvent.class))); MessageStateChangedEvent.class)));
// If message is shared, get group visibility and broadcast event if (shared)
if (shared) {
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(singletonMap(contactId, true)));
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
}
// endTransaction() // endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// close() // close()

View File

@@ -168,7 +168,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertTrue(db.containsGroup(txn, groupId)); assertTrue(db.containsGroup(txn, groupId));
assertTrue(db.containsMessage(txn, messageId)); assertTrue(db.containsMessage(txn, messageId));
assertEquals(groupId, db.getGroupId(txn, messageId));
assertArrayEquals(message.getBody(), assertArrayEquals(message.getBody(),
db.getMessage(txn, messageId).getBody()); db.getMessage(txn, messageId).getBody());
@@ -379,9 +378,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Initially there should be nothing to send // Initially there should be nothing to send
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
// Add some messages to ack // Add some messages to ack
Message message1 = getMessage(groupId); Message message1 = getMessage(groupId);
@@ -390,7 +389,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message1, DELIVERED, true, false, contactId); db.addMessage(txn, message1, DELIVERED, true, false, contactId);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(db.containsAcksToSend(txn, contactId)); assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234); Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -398,7 +400,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1)); db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
// No message IDs should be returned // No message IDs should be returned
assertFalse(db.containsAcksToSend(txn, contactId)); assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234)); assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
// Raise the ack flag again // Raise the ack flag again
@@ -406,7 +411,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.raiseAckFlag(txn, contactId, messageId1); db.raiseAckFlag(txn, contactId, messageId1);
// Both message IDs should be returned // Both message IDs should be returned
assertTrue(db.containsAcksToSend(txn, contactId)); assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
ids = db.getMessagesToAck(txn, contactId, 1234); ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids); assertEquals(asList(messageId, messageId1), ids);
@@ -2021,51 +2029,37 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, UNKNOWN, false, false, null); db.addMessage(txn, message, UNKNOWN, false, false, null);
// There should be no messages to send // There should be no messages to send
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Share the group with the contact - still no messages to send // Share the group with the contact - still no messages to send
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Set the message's state to DELIVERED - still no messages to send // Set the message's state to DELIVERED - still no messages to send
db.setMessageState(txn, messageId, DELIVERED); db.setMessageState(txn, messageId, DELIVERED);
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Share the message - now it should be sendable immediately // Share the message - now it should be sendable immediately
db.setMessageShared(txn, messageId, true); db.setMessageShared(txn, messageId, true);
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); assertEquals(0, db.getNextSendTime(txn, contactId));
// Mark the message as requested - it should still be sendable // Mark the message as requested - it should still be sendable
db.raiseRequestedFlag(txn, contactId, messageId); db.raiseRequestedFlag(txn, contactId, messageId);
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); assertEquals(0, db.getNextSendTime(txn, contactId));
// Update the message's expiry time as though we sent it - now the // Update the message's expiry time as though we sent it - now the
// message should be sendable after one round-trip // message should be sendable after one round-trip
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, 1000);
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should be sendable immediately over a transport with
// lower latency
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
// Update the message's expiry time again - now it should be sendable // Update the message's expiry time again - now it should be sendable
// after two round-trips // after two round-trips
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, 1000);
assertEquals(now + MAX_LATENCY * 4, assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should be sendable immediately over a transport with
// lower latency
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
// Delete the message - there should be no messages to send // Delete the message - there should be no messages to send
db.deleteMessage(txn, messageId); db.deleteMessage(txn, messageId);
assertEquals(Long.MAX_VALUE, assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -2130,8 +2124,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY // The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Time: now + MAX_LATENCY * 2 - 1 // Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable // The message should not yet be sendable
@@ -2174,8 +2167,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY // The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should not be sendable via the same transport // The message should not be sendable via the same transport
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
@@ -2222,8 +2214,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY); db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY // The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// Time: now + MAX_LATENCY * 2 - 1 // Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable // The message should not yet be sendable
@@ -2234,8 +2225,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Reset the retransmission times // Reset the retransmission times
db.resetUnackedMessagesToSend(txn, contactId); db.resetUnackedMessagesToSend(txn, contactId);
// The message should be sendable immediately // The message should have infinitely short expiry
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY)); assertEquals(0, db.getNextSendTime(txn, contactId));
// The message should be sendable // The message should be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
@@ -2588,7 +2579,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertNothingToSendLazily(Database<Connection> db, private void assertNothingToSendLazily(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty()); assertTrue(ids.isEmpty());
@@ -2599,7 +2590,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertOneMessageToSendLazily(Database<Connection> db, private void assertOneMessageToSendLazily(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertTrue( assertTrue(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY); db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids); assertEquals(singletonList(messageId), ids);
@@ -2610,7 +2601,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertNothingToSendEagerly(Database<Connection> db, private void assertNothingToSendEagerly(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertFalse( assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> unacked = Collection<MessageId> unacked =
db.getUnackedMessagesToSend(txn, contactId); db.getUnackedMessagesToSend(txn, contactId);
assertTrue(unacked.isEmpty()); assertTrue(unacked.isEmpty());
@@ -2620,7 +2611,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertOneMessageToSendEagerly(Database<Connection> db, private void assertOneMessageToSendEagerly(Database<Connection> db,
Connection txn) throws Exception { Connection txn) throws Exception {
assertTrue( assertTrue(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true)); db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> unacked = Collection<MessageId> unacked =
db.getUnackedMessagesToSend(txn, contactId); db.getUnackedMessagesToSend(txn, contactId);
assertEquals(singletonList(messageId), unacked); assertEquals(singletonList(messageId), unacked);

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@@ -40,8 +41,13 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
private final KeyPair handshakeKeyPair = private final KeyPair handshakeKeyPair =
new KeyPair(handshakePublicKey, handshakePrivateKey); new KeyPair(handshakePublicKey, handshakePrivateKey);
private final IdentityManagerImpl identityManager = private IdentityManagerImpl identityManager;
new IdentityManagerImpl(db, crypto, authorFactory, clock);
@Before
public void setUp() {
identityManager =
new IdentityManagerImpl(db, crypto, authorFactory, clock);
}
@Test @Test
public void testOpenDatabaseIdentityRegistered() throws Exception { public void testOpenDatabaseIdentityRegistered() throws Exception {

View File

@@ -5,18 +5,20 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPED; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS; import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
@@ -29,18 +31,22 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final EventBus eventBus = context.mock(EventBus.class); private final EventBus eventBus = context.mock(EventBus.class);
private final Clock clock = context.mock(Clock.class); private final Clock clock = context.mock(Clock.class);
private final OpenDatabaseHook hook = context.mock(OpenDatabaseHook.class);
private final Service service = context.mock(Service.class);
private final SecretKey dbKey = getSecretKey(); private final SecretKey dbKey = getSecretKey();
private final LifecycleManagerImpl lifecycleManager = private LifecycleManagerImpl lifecycleManager;
new LifecycleManagerImpl(db, eventBus, clock);
@Before
public void setUp() {
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
}
@Test @Test
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception { public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
AtomicBoolean called = new AtomicBoolean(false);
OpenDatabaseHook hook = transaction -> called.set(true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
@@ -49,7 +55,6 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn); oneOf(db).removeTemporaryMessages(txn);
oneOf(hook).onDatabaseOpened(txn);
allowing(eventBus).broadcast(with(any(LifecycleEvent.class))); allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}}); }});
@@ -57,38 +62,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey)); assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState()); assertEquals(RUNNING, lifecycleManager.getLifecycleState());
} assertTrue(called.get());
@Test
public void testServicesAreStartedAndStopped() throws Exception {
long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn);
oneOf(service).startService();
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
lifecycleManager.registerService(service);
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied();
context.checking(new Expectations() {{
oneOf(db).close();
oneOf(service).stopService();
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
lifecycleManager.stopServices();
assertEquals(STOPPED, lifecycleManager.getLifecycleState());
} }
@Test @Test
@@ -115,31 +89,6 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
assertEquals(STARTING, lifecycleManager.getLifecycleState()); assertEquals(STARTING, lifecycleManager.getLifecycleState());
} }
@Test
public void testSecondCallToStartServicesReturnsEarly() throws Exception {
long now = System.currentTimeMillis();
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn);
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied();
// Calling startServices() again should not try to open the DB or
// start the services again
assertEquals(ALREADY_RUNNING, lifecycleManager.startServices(dbKey));
assertEquals(RUNNING, lifecycleManager.getLifecycleState());
}
@Test @Test
public void testSecondCallToStopServicesReturnsEarly() throws Exception { public void testSecondCallToStopServicesReturnsEarly() throws Exception {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@@ -152,7 +101,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn); oneOf(db).removeTemporaryMessages(txn);
allowing(eventBus).broadcast(with(any(LifecycleEvent.class))); exactly(2).of(eventBus).broadcast(with(any(LifecycleEvent.class)));
}}); }});
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey)); assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
@@ -160,17 +109,17 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(LifecycleEvent.class)));
oneOf(db).close(); oneOf(db).close();
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}}); }});
lifecycleManager.stopServices(); lifecycleManager.stopServices();
assertEquals(STOPPED, lifecycleManager.getLifecycleState()); assertEquals(STOPPING, lifecycleManager.getLifecycleState());
context.assertIsSatisfied(); context.assertIsSatisfied();
// Calling stopServices() again should not broadcast another event or // Calling stopServices() again should not broadcast another event or
// try to close the DB again // try to close the DB again
lifecycleManager.stopServices(); lifecycleManager.stopServices();
assertEquals(STOPPED, lifecycleManager.getLifecycleState()); assertEquals(STOPPING, lifecycleManager.getLifecycleState());
} }
} }

View File

@@ -1,143 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
public class ContactMailboxClientTest extends BrambleMockTestCase {
private final MailboxWorkerFactory workerFactory =
context.mock(MailboxWorkerFactory.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final TorReachabilityMonitor reachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final MailboxWorker uploadWorker =
context.mock(MailboxWorker.class, "uploadWorker");
private final MailboxWorker downloadWorker =
context.mock(MailboxWorker.class, "downloadWorker");
private final MailboxProperties properties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxFolderId inboxId =
requireNonNull(properties.getInboxId());
private final MailboxFolderId outboxId =
requireNonNull(properties.getOutboxId());
private final ContactId contactId = getContactId();
private final ContactMailboxClient client =
new ContactMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor);
@Test
public void testStartAndDestroyWithNoContactsAssigned() {
client.start();
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignContactForUploadAndDestroyClient() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartUploadWorker();
client.assignContactForUpload(contactId, properties, outboxId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(uploadWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignAndDeassignContactForUpload() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartUploadWorker();
client.assignContactForUpload(contactId, properties, outboxId);
// When the contact is deassigned, the worker should be destroyed
expectDestroyWorker(uploadWorker);
client.deassignContactForUpload(contactId);
context.assertIsSatisfied();
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignContactForDownloadAndDestroyClient() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartDownloadWorker();
client.assignContactForDownload(contactId, properties, inboxId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(downloadWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignAndDeassignContactForDownload() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartDownloadWorker();
client.assignContactForDownload(contactId, properties, inboxId);
// When the contact is deassigned, the worker should be destroyed
expectDestroyWorker(downloadWorker);
client.deassignContactForDownload(contactId);
context.assertIsSatisfied();
expectDestroyConnectivityChecker();
client.destroy();
}
private void expectCreateAndStartUploadWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createUploadWorker(connectivityChecker,
properties, outboxId, contactId);
will(returnValue(uploadWorker));
oneOf(uploadWorker).start();
}});
}
private void expectCreateAndStartDownloadWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createDownloadWorkerForContactMailbox(
connectivityChecker, reachabilityMonitor, properties);
will(returnValue(downloadWorker));
oneOf(downloadWorker).start();
}});
}
private void expectDestroyWorker(MailboxWorker worker) {
context.checking(new Expectations() {{
oneOf(worker).destroy();
}});
}
private void expectDestroyConnectivityChecker() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).destroy();
}});
}
}

View File

@@ -1,68 +1,85 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS; import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
public class ContactMailboxDownloadWorkerTest public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
extends MailboxDownloadWorkerTest<ContactMailboxDownloadWorker> {
public ContactMailboxDownloadWorkerTest() { private final ConnectivityChecker connectivityChecker =
mailboxProperties = getMailboxProperties(false, CLIENT_SUPPORTS); context.mock(ConnectivityChecker.class);
private final TorReachabilityMonitor torReachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final MailboxFileManager mailboxFileManager =
context.mock(MailboxFileManager.class);
private final MailboxProperties mailboxProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
private final MailboxFile file1 =
new MailboxFile(new MailboxFileId(getRandomId()), now - 1);
private final MailboxFile file2 =
new MailboxFile(new MailboxFileId(getRandomId()), now);
private final List<MailboxFile> files = asList(file1, file2);
private File testDir, tempFile;
private ContactMailboxDownloadWorker worker;
@Before
public void setUp() {
testDir = getTestDirectory();
tempFile = new File(testDir, "temp");
worker = new ContactMailboxDownloadWorker(connectivityChecker, worker = new ContactMailboxDownloadWorker(connectivityChecker,
torReachabilityMonitor, mailboxApiCaller, mailboxApi, torReachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, mailboxProperties); mailboxFileManager, mailboxProperties);
} }
@Test @After
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() { public void tearDown() {
// When the worker is started it should start a connectivity check deleteTestDirectory(testDir);
expectStartConnectivityCheck();
worker.start();
// When the worker is destroyed it should remove the connectivity
// and reachability observers
expectRemoveObservers();
worker.destroy();
} }
@Test @Test
public void testChecksForFilesWhenConnectivityCheckSucceeds() public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
throws Exception {
// When the worker is started it should start a connectivity check // When the worker is started it should start a connectivity check
expectStartConnectivityCheck(); context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
worker.start(); worker.start();
// When the connectivity check succeeds, a list-inbox task should be
// started for the first download cycle
AtomicReference<ApiCall> listTask = new AtomicReference<>();
expectStartTask(listTask);
worker.onConnectivityCheckSucceeded();
// When the list-inbox tasks runs and finds no files to download,
// it should add a Tor reachability observer
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
expectAddReachabilityObserver();
assertFalse(listTask.get().callApi());
// When the reachability observer is called, a list-inbox task should
// be started for the second download cycle
expectStartTask(listTask);
worker.onTorReachable();
// When the list-inbox tasks runs and finds no files to download,
// it should finish the second download cycle
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
assertFalse(listTask.get().callApi());
// When the worker is destroyed it should remove the connectivity // When the worker is destroyed it should remove the connectivity
// and reachability observers // and reachability observers
expectRemoveObservers(); context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
worker.destroy(); worker.destroy();
} }
@@ -70,67 +87,129 @@ public class ContactMailboxDownloadWorkerTest
public void testDownloadsFilesWhenConnectivityCheckSucceeds() public void testDownloadsFilesWhenConnectivityCheckSucceeds()
throws Exception { throws Exception {
// When the worker is started it should start a connectivity check // When the worker is started it should start a connectivity check
expectStartConnectivityCheck(); context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
worker.start(); worker.start();
// When the connectivity check succeeds, a list-inbox task should be // When the connectivity check succeeds, a list-inbox task should be
// started for the first download cycle // started for the first download cycle
AtomicReference<ApiCall> listTask = new AtomicReference<>(); AtomicReference<ApiCall> listTask = new AtomicReference<>(null);
expectStartTask(listTask); context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
}});
worker.onConnectivityCheckSucceeded(); worker.onConnectivityCheckSucceeded();
// When the list-inbox tasks runs and finds some files to download, // When the list-inbox tasks runs and finds some files to download,
// it should start a download task for the first file // it should start a download task for the first file
AtomicReference<ApiCall> downloadTask = new AtomicReference<>(); AtomicReference<ApiCall> downloadTask = new AtomicReference<>(null);
expectCheckForFiles(mailboxProperties.getInboxId(), files); context.checking(new Expectations() {{
expectStartTask(downloadTask); oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(files));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0));
}});
assertFalse(listTask.get().callApi()); assertFalse(listTask.get().callApi());
// When the first download task runs it should download the file to the // When the first download task runs it should download the file to the
// location provided by the file manager and start a delete task // location provided by the file manager and start a delete task
AtomicReference<ApiCall> deleteTask = new AtomicReference<>(); AtomicReference<ApiCall> deleteTask = new AtomicReference<>(null);
expectDownloadFile(mailboxProperties.getInboxId(), file1); context.checking(new Expectations() {{
expectStartTask(deleteTask); oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file1.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0));
}});
assertFalse(downloadTask.get().callApi()); assertFalse(downloadTask.get().callApi());
// When the first delete task runs it should delete the file, ignore // When the first delete task runs it should delete the file, ignore
// the tolerable failure, and start a download task for the next file // the tolerable failure, and start a download task for the next file
expectDeleteFile(mailboxProperties.getInboxId(), file1, true); context.checking(new Expectations() {{
expectStartTask(downloadTask); oneOf(mailboxApi).deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file1.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0));
}});
assertFalse(deleteTask.get().callApi()); assertFalse(deleteTask.get().callApi());
// When the second download task runs it should download the file to // When the second download task runs it should download the file to
// the location provided by the file manager and start a delete task // the location provided by the file manager and start a delete task
expectDownloadFile(mailboxProperties.getInboxId(), file2); context.checking(new Expectations() {{
expectStartTask(deleteTask); oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file2.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0));
}});
assertFalse(downloadTask.get().callApi()); assertFalse(downloadTask.get().callApi());
// When the second delete task runs it should delete the file and // When the second delete task runs it should delete the file and
// start a list-inbox task to check for files that may have arrived // start a list-inbox task to check for files that may have arrived
// since the first download cycle started // since the first download cycle started
expectDeleteFile(mailboxProperties.getInboxId(), file2, false); context.checking(new Expectations() {{
expectStartTask(listTask); oneOf(mailboxApi).deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file2.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
}});
assertFalse(deleteTask.get().callApi()); assertFalse(deleteTask.get().callApi());
// When the list-inbox tasks runs and finds no more files to download, // When the list-inbox tasks runs and finds no more files to download,
// it should add a Tor reachability observer // it should add a Tor reachability observer
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList()); context.checking(new Expectations() {{
expectAddReachabilityObserver(); oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(emptyList()));
oneOf(torReachabilityMonitor).addOneShotObserver(worker);
}});
assertFalse(listTask.get().callApi()); assertFalse(listTask.get().callApi());
// When the reachability observer is called, a list-inbox task should // When the reachability observer is called, a list-inbox task should
// be started for the second download cycle // be started for the second download cycle
expectStartTask(listTask); context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
}});
worker.onTorReachable(); worker.onTorReachable();
// When the list-inbox tasks runs and finds no more files to download, // When the list-inbox tasks runs and finds no more files to download,
// it should finish the second download cycle // it should finish the second download cycle
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList()); context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(emptyList()));
}});
assertFalse(listTask.get().callApi()); assertFalse(listTask.get().callApi());
// When the worker is destroyed it should remove the connectivity // When the worker is destroyed it should remove the connectivity
// and reachability observers // and reachability observers
expectRemoveObservers(); context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
worker.destroy(); worker.destroy();
} }
} }

View File

@@ -68,10 +68,7 @@ public class MailboxApiTest extends BrambleTestCase {
return client; return client;
} }
}; };
// We aren't using a real onion address, so use the given address verbatim private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
private final UrlConverter urlConverter = onion -> onion;
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider,
urlConverter);
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId()); private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId()); private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId());
@@ -630,7 +627,6 @@ public class MailboxApiTest extends BrambleTestCase {
server.enqueue(new MockResponse().setBody(invalidResponse3)); server.enqueue(new MockResponse().setBody(invalidResponse3));
server.enqueue(new MockResponse().setBody(invalidResponse4)); server.enqueue(new MockResponse().setBody(invalidResponse4));
server.enqueue(new MockResponse().setResponseCode(401)); server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(404));
server.enqueue(new MockResponse().setResponseCode(500)); server.enqueue(new MockResponse().setResponseCode(500));
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
@@ -707,21 +703,13 @@ public class MailboxApiTest extends BrambleTestCase {
assertEquals("GET", request8.getMethod()); assertEquals("GET", request8.getMethod());
assertToken(request8, token); assertToken(request8, token);
// 404 not found // 500 internal server error
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class,
api.getFiles(properties, contactInboxId)); () -> api.getFiles(properties, contactInboxId));
RecordedRequest request9 = server.takeRequest(); RecordedRequest request9 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request9.getPath()); assertEquals("/files/" + contactInboxId, request9.getPath());
assertEquals("GET", request9.getMethod()); assertEquals("GET", request9.getMethod());
assertToken(request9, token); assertToken(request9, token);
// 500 internal server error
assertThrows(ApiException.class,
() -> api.getFiles(properties, contactInboxId));
RecordedRequest request10 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request10.getPath());
assertEquals("GET", request10.getMethod());
assertToken(request10, token);
} }
@Test @Test

Some files were not shown because too many files have changed in this diff Show More