Compare commits

..

68 Commits

Author SHA1 Message Date
akwizgran
a5563ead28 Bump version numbers for 1.4.10 release. 2022-07-04 16:08:53 +01:00
akwizgran
e15f49fde7 Update translations. 2022-07-04 15:59:57 +01:00
Torsten Grote
1531a24b2d Merge branch '1499-do-not-set-tor-config-during-shutdown' into 'master'
Don't set "DisableNetwork 1" during shutdown

See merge request briar/briar!1684
2022-07-01 13:10:02 +00:00
akwizgran
2298818af5 Don't set "DisableNetwork 1" during shutdown.
This is redundant now that we start from the default config every time.
2022-07-01 12:30:46 +01:00
akwizgran
0ae5361281 Merge branch '1777-lifecycle-manager' into 'master'
Allow process to exit if an exception is thrown during shutdown

Closes #1777

See merge request briar/briar!1668
2022-06-29 14:23:16 +00:00
Torsten Grote
d8e26eebbe Merge branch '1499-do-not-apply-redundant-settings' into 'master'
Start from default Tor config every time, don't apply redundant settings

See merge request briar/briar!1681
2022-06-29 13:50:01 +00:00
akwizgran
692e353046 Convert comments to javadocs. 2022-06-29 13:54:30 +01:00
akwizgran
b9ba7aded5 Merge branch 'string-utils-checked-exceptions' into 'master'
Let StringUtils throw FormatException instead of IllegalArgumentException

See merge request briar/briar!1682
2022-06-29 12:21:29 +00:00
Sebastian Kürten
4bca9decc1 Let StringUtils throw FormatException instead of IllegalArgumentException 2022-06-29 14:13:42 +02:00
akwizgran
7bbe9068bb Start from the default Tor config every time.
Don't apply settings to Tor unless they've changed.
2022-06-28 12:42:55 +01:00
akwizgran
63060679a3 Merge branch '2342-mailbox-setup-stuck' into 'master'
Ensure that mailbox setup can proceed after activity was destroyed

Closes #2342

See merge request briar/briar!1678
2022-06-26 13:04:34 +00:00
Torsten Grote
ddb759dbb8 Merge branch 'mailbox-base-url-refactoring' into 'master'
Refactor MailboxProperties to hold raw onion address

See merge request briar/briar!1680
2022-06-23 17:27:17 +00:00
akwizgran
592daf9c20 Bump version numbers for 1.4.9 release. 2022-06-23 14:55:06 +01:00
akwizgran
3922270db1 Merge branch 'update-bridges' into 'master'
Update Tor bridges

See merge request briar/briar!1679
2022-06-23 13:50:12 +00:00
akwizgran
feb8854678 Add @Inject constructor. 2022-06-23 14:22:25 +01:00
Torsten Grote
4ba4e41e69 Merge branch '2294-contact-list-worker' into 'master'
Mailbox worker for updating our own mailbox's contact list

Closes #2294

See merge request briar/briar!1677
2022-06-23 12:19:32 +00:00
akwizgran
1f699238a9 Add some non-default bridges. 2022-06-23 12:23:50 +01:00
akwizgran
b8e91a12e8 Remove some failing bridges. 2022-06-23 12:18:38 +01:00
akwizgran
06eb01ab0a Update translations. 2022-06-23 11:43:26 +01:00
akwizgran
d82509f3ce Address review feedback. 2022-06-23 11:00:13 +01:00
Torsten Grote
b01c306500 Merge branch '2289-client-for-contacts-mailbox' into 'master'
Mailbox client for a contact's mailbox

Closes #2289

See merge request briar/briar!1674
2022-06-22 17:09:37 +00:00
Torsten Grote
61e7635b9f Merge branch 'windows-tor-plugin' into 'master'
Add Tor plugin for Windows

See merge request briar/briar!1666
2022-06-22 17:08:23 +00:00
akwizgran
f2f356cbd4 Merge branch '2340-unlink-offline-crash' into 'master'
Don't crash when offline while unlink dialog gets shown

Closes #2340

See merge request briar/briar!1676
2022-06-22 16:41:26 +00:00
Torsten Grote
28f3ab1310 Dismiss unlink dialog when going offline 2022-06-22 13:25:12 -03:00
Torsten Grote
1af52b21d5 Ensure that mailbox setup can proceed after activity was destroyed 2022-06-22 11:44:07 -03:00
akwizgran
8bb3a83ccb Add tests for contact list worker. 2022-06-22 13:59:50 +01:00
Torsten Grote
a742b007ef Don't crash when offline while unlink dialog gets shown 2022-06-22 08:58:59 -03:00
Torsten Grote
6bfd7bcc4f Merge branch '2338-make-headless-platform-jars-depend-on-jar-task' into 'master'
Make headless platform jars depend on main jar task

Closes #2338

See merge request briar/briar!1675
2022-06-22 11:28:12 +00:00
Sebastian Kürten
17f5fc7518 Make headless platform jars depend on main jar task 2022-06-22 10:30:32 +02:00
akwizgran
8dcf988399 Add contact list worker for own mailbox. 2022-06-20 17:55:21 +01:00
akwizgran
05bf3833cf No need to use @Before to create stateful test objects. 2022-06-20 16:24:55 +01:00
akwizgran
c39c2ce124 Fetch supported API versions during connectivity check. 2022-06-20 13:55:05 +01:00
akwizgran
0b93af5d71 Add some logging. 2022-06-20 13:46:09 +01:00
akwizgran
f8e3579a92 Add tests for ContactMailboxClient. 2022-06-20 13:33:32 +01:00
Torsten Grote
54e434d812 Merge branch '2291-mailbox-upload-worker' into 'master'
Mailbox upload worker

Closes #2291

See merge request briar/briar!1673
2022-06-20 11:39:44 +00:00
akwizgran
13c3974f73 Implement client for a contact's mailbox. 2022-06-20 12:24:21 +01:00
akwizgran
aeb2a370e1 Return safely if destroy() is called before start(). 2022-06-20 12:20:15 +01:00
akwizgran
0aff23a067 Add MailboxWorkerFactory. 2022-06-20 11:31:37 +01:00
akwizgran
a2a2da0260 Make MailboxSettingsManager a singleton, now that it accepts hooks. 2022-06-20 11:23:26 +01:00
akwizgran
4d7a3bca62 Address review feedback. 2022-06-20 10:41:13 +01:00
akwizgran
91d5698fe9 Fix a typo. 2022-06-17 16:36:07 +01:00
akwizgran
7266c6ee6b Create temp file before requesting plugin. 2022-06-17 16:34:21 +01:00
akwizgran
06b539b911 Tests for MailboxUploadWorker. 2022-06-17 16:28:04 +01:00
akwizgran
486ba4a3fc Merge branch '2337-dont-show-offline-screen-after-pairing' into 'master'
Ignore offline event in Paired state (when success screen is shown)

Closes #2337

See merge request briar/briar!1672
2022-06-17 13:16:28 +00:00
Torsten Grote
7f987667fe Merge branch '2336-get-next-send-time' into 'master'
Consider latency when getting next send time from DB

Closes #2336

See merge request briar/briar!1671
2022-06-17 13:14:38 +00:00
Torsten Grote
8d22a0ffaf Merge branch 'do-not-interpolate-app-name' into 'master'
Don't interpolate the app name into strings

See merge request briar/briar!1669
2022-06-17 13:13:04 +00:00
Torsten Grote
43d28608f5 Merge branch '2291-mailbox-upload-plumbing' into 'master'
Plumbing for mailbox upload worker

See merge request briar/briar!1670
2022-06-17 13:07:22 +00:00
Torsten Grote
c84d3f7707 Ignore offline event in Paired state (when success screen is shown) 2022-06-17 10:05:00 -03:00
akwizgran
2843e15905 Add mailbox upload worker. 2022-06-16 18:11:52 +01:00
akwizgran
a2fb388aa6 Add creation of files for upload by MailboxFileManager. 2022-06-16 18:11:52 +01:00
akwizgran
b7b253cf24 Clear reference to API call when download cycle finishes. 2022-06-16 18:11:52 +01:00
akwizgran
f05e9dd746 Fix a couple of test assertions. 2022-06-16 18:11:52 +01:00
akwizgran
e2a63ee361 Consider latency when getting next send time from DB. 2022-06-16 17:05:30 +01:00
akwizgran
ff9f706670 Add plumbing for creating outgoing sync sessions. 2022-06-16 15:51:16 +01:00
akwizgran
10ab60569b Replace DeferredSendHandler with OutgoingSessionRecord. 2022-06-16 15:51:15 +01:00
akwizgran
d77d1d67aa Include new visibility in GroupVisibilityUpdatedEvent. 2022-06-16 15:51:15 +01:00
akwizgran
924425522a Split containsAnythingToSend() into methods for acks and messages. 2022-06-16 15:51:15 +01:00
akwizgran
356e0ee07b Move MAX_LATENCY to MailboxConstants. 2022-06-16 15:51:15 +01:00
akwizgran
61658655ff Merge branch '2326-fetch-versions' into 'master'
Use /versions for mailbox connectivity check

Closes #2326

See merge request briar/briar!1665
2022-06-14 12:29:31 +00:00
akwizgran
40086ffde2 Don't interpolate the app name into strings. 2022-06-14 10:30:05 +01:00
Torsten Grote
1551142e98 Merge branch '2157-2158-xiaomi-power-setup' into 'master'
Adapt Xiaomi power setup for MIUI 12.5

Closes #2158 and #2157

See merge request briar/briar!1667
2022-06-13 13:12:50 +00:00
Torsten Grote
1c6fb6491a Use /versions for mailbox connectivity check
Briar's mailbox status screen used the status API endpoint for its connectivity check. Now, it uses the versions endpoint instead, so that if we've warned the user that Briar and the Mailbox are using incompatible API versions, and the user has upgraded one of the apps to fix the issue, the user can use the "check connection" button in the status screen to check that the issue has been fixed.

(This is specifically needed for the case where the user has upgraded the Mailbox, because in the case where the user has upgraded Briar, Briar should automatically check the mailbox's API versions when it comes back online after upgrading.)
2022-06-13 10:07:40 -03:00
akwizgran
cfd4e85e77 Remove package names that are now provided by dont-kill-me-lib. 2022-06-13 13:48:23 +01:00
akwizgran
4d6abfabf7 Adapt Xiaomi power setup for MIUI 12.5. 2022-06-13 11:32:36 +01:00
akwizgran
e481a02126 Shutdown from background if BriarService is recreated. 2022-06-09 18:10:24 +01:00
akwizgran
825dff27fc Exit if BriarService finds lifecycle already running. 2022-06-09 18:06:08 +01:00
akwizgran
de3a87fff5 Return early when starting/stopping if not in expected state. 2022-06-09 18:01:32 +01:00
akwizgran
85d1addd04 Continue shutdown if an exception is thrown. 2022-06-09 17:16:02 +01:00
149 changed files with 4558 additions and 1465 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
@NotNullByDefault
public interface ConnectionManager {
@@ -45,6 +46,14 @@ public interface ConnectionManager {
void manageOutgoingConnection(ContactId c, TransportId t,
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.
*/

View File

@@ -126,16 +126,11 @@ public interface DatabaseComponent extends TransactionManager {
TransportKeys k) throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* Returns true if there are any acks to send to the given contact.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) throws DbException;
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException;
/**
* Returns true if the database contains the given contact for the given
@@ -161,6 +156,18 @@ public interface DatabaseComponent extends TransactionManager {
*/
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.
* <p/>
@@ -534,15 +541,18 @@ public interface DatabaseComponent extends TransactionManager {
*/
long getNextCleanupDeadline(Transaction txn) throws DbException;
/*
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. 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.
* message is due to be sent to the given contact over a transport with
* the given latency.
* <p>
* 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/>
* Read-only.
*/
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
long getNextSendTime(Transaction txn, ContactId c, long maxLatency)
throws DbException;
/**
* Returns the pending contact with the given ID.

View File

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

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.DAYS;
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_PAYLOAD_LENGTH;
@@ -65,4 +66,8 @@ public interface MailboxConstants {
*/
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

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

View File

@@ -11,7 +11,7 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class MailboxProperties {
private final String baseUrl;
private final String onion;
private final MailboxAuthToken authToken;
private final boolean owner;
private final List<MailboxVersion> serverSupports;
@@ -23,9 +23,9 @@ public class MailboxProperties {
/**
* Constructor for properties used by the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
public MailboxProperties(String onion, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports) {
this.baseUrl = baseUrl;
this.onion = onion;
this.authToken = authToken;
this.owner = true;
this.serverSupports = serverSupports;
@@ -36,10 +36,10 @@ public class MailboxProperties {
/**
* Constructor for properties used by a contact of the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
public MailboxProperties(String onion, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
MailboxFolderId outboxId) {
this.baseUrl = baseUrl;
this.onion = onion;
this.authToken = authToken;
this.owner = false;
this.serverSupports = serverSupports;
@@ -47,13 +47,11 @@ public class MailboxProperties {
this.outboxId = outboxId;
}
public String getBaseUrl() {
return baseUrl;
}
/**
* Returns the onion address of the mailbox, excluding the .onion suffix.
*/
public String getOnion() {
return baseUrl.replaceFirst("^http://", "")
.replaceFirst("\\.onion$", "");
return onion;
}
public MailboxAuthToken getAuthToken() {

View File

@@ -35,6 +35,9 @@ public interface MailboxSettingsManager {
void recordSuccessfulConnection(Transaction txn, long now)
throws DbException;
void recordSuccessfulConnection(Transaction txn, long now,
List<MailboxVersion> versions) throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException;

View File

@@ -29,19 +29,35 @@ public interface MailboxUpdateManager {
/**
* 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;
/**
* The required properties of an update message with a mailbox.
* The onion address of the mailbox, excluding the .onion suffix.
*/
String PROP_KEY_ONION = "onion";
/**
* A bearer token for accessing the mailbox (64 hex digits).
*/
String PROP_KEY_AUTHTOKEN = "authToken";
/**
* A folder ID for downloading messages (64 hex digits).
*/
String PROP_KEY_INBOXID = "inboxId";
/**
* A folder ID for uploading messages (64 hex digits).
*/
String PROP_KEY_OUTBOXID = "outboxId";
/**
* Length of the Onion property.
* Length of the {@link #PROP_KEY_ONION} property.
*/
int PROP_ONION_LENGTH = 56;
@@ -63,9 +79,26 @@ public interface MailboxUpdateManager {
*/
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
/**
* 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)
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
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException;

View File

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

View File

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

View File

@@ -1,25 +0,0 @@
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

@@ -1,15 +0,0 @@
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

@@ -0,0 +1,37 @@
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,12 +12,30 @@ import javax.annotation.Nullable;
@NotNullByDefault
public interface SyncSessionFactory {
/**
* Creates a session for receiving data from a contact.
*/
SyncSession createIncomingSession(ContactId c, InputStream in,
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,
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,
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority);

View File

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

View File

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

View File

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

View File

@@ -230,14 +230,14 @@ public class TestUtils {
public static MailboxProperties getMailboxProperties(boolean owner,
List<MailboxVersion> serverSupports) {
String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
String onion = getRandomString(56);
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
if (owner) {
return new MailboxProperties(baseUrl, authToken, serverSupports);
return new MailboxProperties(onion, authToken, serverSupports);
}
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxProperties(baseUrl, authToken, serverSupports,
return new MailboxProperties(onion, authToken, serverSupports,
inboxId, outboxId);
}

View File

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

View File

@@ -456,8 +456,7 @@ class ClientHelperImpl implements ClientHelper {
checkLength(inboxId, UniqueId.LENGTH);
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
checkLength(outboxId, UniqueId.LENGTH);
String baseUrl = "http://" + onion + ".onion"; // TODO
MailboxProperties props = new MailboxProperties(baseUrl,
MailboxProperties props = new MailboxProperties(onion,
new MailboxAuthToken(authToken), serverSupportsList,
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
return new MailboxUpdateWithMailbox(clientSupportsList, props);

View File

@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
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.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
@@ -100,7 +101,16 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w));
syncSessionFactory, transportPropertyManager, c, t, w, null));
}
@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

View File

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

View File

@@ -163,16 +163,11 @@ interface Database<T> {
throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* Returns true if there are any acks to send to the given contact.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
boolean eager) throws DbException;
boolean containsAcksToSend(T txn, ContactId c) throws DbException;
/**
* Returns true if the database contains the given contact for the given
@@ -212,6 +207,18 @@ interface Database<T> {
*/
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.
* <p/>
@@ -580,13 +587,16 @@ interface Database<T> {
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. 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.
* message is due to be sent to the given contact over a transport with
* the given latency.
* <p>
* 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/>
* Read-only.
*/
long getNextSendTime(T txn, ContactId c) throws DbException;
long getNextSendTime(T txn, ContactId c, long maxLatency)
throws DbException;
/**
* Returns the pending contact with the given ID.

View File

@@ -342,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
long maxLatency, boolean eager) throws DbException {
public boolean containsAcksToSend(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsAnythingToSend(txn, c, maxLatency, eager);
return db.containsAcksToSend(txn, c);
}
@Override
@@ -373,6 +373,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
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
public boolean containsPendingContact(Transaction transaction,
PendingContactId p) throws DbException {
@@ -805,10 +814,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException {
public long getNextSendTime(Transaction transaction, ContactId c,
long maxLatency) throws DbException {
T txn = unbox(transaction);
return db.getNextSendTime(txn, c);
return db.getNextSendTime(txn, c, maxLatency);
}
@Override
@@ -1016,7 +1025,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
affected));
}
@Override
@@ -1141,7 +1151,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED);
List<ContactId> affected = singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected));
}
@Override

View File

@@ -1147,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public boolean containsAnythingToSend(Connection txn, ContactId c,
long maxLatency, boolean eager) throws DbException {
public boolean containsAcksToSend(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
@@ -1160,34 +1160,7 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean acksToSend = rs.next();
rs.close();
ps.close();
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;
return acksToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
@@ -1307,6 +1280,46 @@ 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
public boolean containsPendingContact(Connection txn, PendingContactId p)
throws DbException {
@@ -2477,12 +2490,28 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public long getNextSendTime(Connection txn, ContactId c)
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT expiry FROM statuses"
// Are any messages sendable immediately?
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 = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " 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.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
@@ -29,10 +29,12 @@ 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.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.RUNNING;
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.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;
@@ -60,12 +62,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final List<Service> services;
private final List<OpenDatabaseHook> openDatabaseHooks;
private final List<ExecutorService> executors;
private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1);
private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private volatile LifecycleState state = STARTING;
private final AtomicReference<LifecycleState> state =
new AtomicReference<>(CREATED);
@Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
@@ -102,8 +103,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Override
public StartResult startServices(SecretKey dbKey) {
if (!startStopSemaphore.tryAcquire()) {
LOG.info("Already starting or stopping");
if (!state.compareAndSet(CREATED, STARTING)) {
LOG.warning("Already running");
return ALREADY_RUNNING;
}
long now = clock.currentTimeMillis();
@@ -135,7 +136,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
});
LOG.info("Starting services");
state = STARTING_SERVICES;
state.set(STARTING_SERVICES);
dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
@@ -148,7 +149,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}
}
state = RUNNING;
state.set(RUNNING);
startupLatch.countDown();
eventBus.broadcast(new LifecycleEvent(RUNNING));
return SUCCESS;
@@ -164,63 +165,58 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} catch (ServiceException e) {
logException(LOG, WARNING, e);
return SERVICE_ERROR;
} finally {
startStopSemaphore.release();
}
}
@Override
public void onDatabaseMigration() {
state = MIGRATING_DATABASE;
state.set(MIGRATING_DATABASE);
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
}
@Override
public void onDatabaseCompaction() {
state = COMPACTING_DATABASE;
state.set(COMPACTING_DATABASE);
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
}
@Override
public void stopServices() {
try {
startStopSemaphore.acquire();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting to stop services");
if (!state.compareAndSet(RUNNING, STOPPING)) {
LOG.warning("Not running");
return;
}
try {
if (state == STOPPING) {
LOG.info("Already stopped");
return;
}
LOG.info("Stopping services");
state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING));
for (Service s : services) {
LOG.info("Stopping services");
eventBus.broadcast(new LifecycleEvent(STOPPING));
for (Service s : services) {
try {
long start = now();
s.stopService();
if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Stopping service "
+ s.getClass().getSimpleName(), start);
}
} catch (ServiceException e) {
logException(LOG, WARNING, e);
}
for (ExecutorService e : executors) {
if (LOG.isLoggable(FINE)) {
LOG.fine("Stopping executor "
+ e.getClass().getSimpleName());
}
e.shutdownNow();
}
for (ExecutorService e : executors) {
if (LOG.isLoggable(FINE)) {
LOG.fine("Stopping executor "
+ e.getClass().getSimpleName());
}
e.shutdownNow();
}
try {
long start = now();
db.close();
logDuration(LOG, "Closing database", start);
shutdownLatch.countDown();
} catch (DbException | ServiceException e) {
} catch (DbException e) {
logException(LOG, WARNING, e);
} finally {
startStopSemaphore.release();
}
state.set(STOPPED);
shutdownLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STOPPED));
}
@Override
@@ -240,6 +236,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Override
public LifecycleState getLifecycleState() {
return state;
return state.get();
}
}

View File

@@ -0,0 +1,122 @@
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 javax.inject.Inject;
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;
@Inject
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();
}
@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

@@ -90,7 +90,7 @@ class ContactMailboxDownloadWorker implements MailboxWorker,
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) throw new IllegalStateException();
if (state != State.CREATED) return;
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
@@ -146,10 +146,12 @@ class ContactMailboxDownloadWorker implements MailboxWorker,
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) {

View File

@@ -42,18 +42,22 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@NotNullByDefault
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 =
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
private static final MediaType FILE =
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
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider,
UrlConverter urlConverter) {
this.httpClientProvider = httpClientProvider;
this.urlConverter = urlConverter;
}
@Override
@@ -78,7 +82,7 @@ class MailboxApiImpl implements MailboxApi {
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + "/setup")
.url(getBaseUrl(properties) + "/setup")
.put(EMPTY_REQUEST)
.build();
OkHttpClient client = httpClientProvider.get();
@@ -93,7 +97,7 @@ class MailboxApiImpl implements MailboxApi {
if (tokenNode == null) {
throw new ApiException();
}
return new MailboxProperties(properties.getBaseUrl(),
return new MailboxProperties(properties.getOnion(),
MailboxAuthToken.fromString(tokenNode.textValue()),
parseServerSupports(node));
} catch (JacksonException | InvalidMailboxIdException e) {
@@ -137,7 +141,7 @@ class MailboxApiImpl implements MailboxApi {
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + "/")
.url(getBaseUrl(properties) + "/")
.delete()
.build();
OkHttpClient client = httpClientProvider.get();
@@ -162,7 +166,7 @@ class MailboxApiImpl implements MailboxApi {
public void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException();
String url = properties.getBaseUrl() + "/contacts/" +
String url = getBaseUrl(properties) + "/contacts/" +
contactId.getInt();
Request request = getRequestBuilder(properties.getAuthToken())
.delete()
@@ -266,7 +270,7 @@ class MailboxApiImpl implements MailboxApi {
String path = "/files/" + folderId + "/" + fileId;
Request request = getRequestBuilder(properties.getAuthToken())
.delete()
.url(properties.getBaseUrl() + path)
.url(getBaseUrl(properties) + path)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
@@ -308,7 +312,7 @@ class MailboxApiImpl implements MailboxApi {
private Response sendGetRequest(MailboxProperties properties, String path)
throws IOException {
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + path)
.url(getBaseUrl(properties) + path)
.build();
OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute();
@@ -317,7 +321,7 @@ class MailboxApiImpl implements MailboxApi {
private Response sendPostRequest(MailboxProperties properties, String path,
RequestBody body) throws IOException {
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + path)
.url(getBaseUrl(properties) + path)
.post(body)
.build();
OkHttpClient client = httpClientProvider.get();
@@ -339,4 +343,7 @@ class MailboxApiImpl implements MailboxApi {
return (ArrayNode) arrayNode;
}
private String getBaseUrl(MailboxProperties properties) {
return urlConverter.convertOnionToBaseUrl(properties.getOnion());
}
}

View File

@@ -0,0 +1,46 @@
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.
*/
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.
*/
void assignContactForDownload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for download.
*/
void deassignContactForDownload(ContactId c);
}

View File

@@ -1,6 +1,8 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File;
import java.io.IOException;
@@ -16,6 +18,14 @@ interface MailboxFileManager {
*/
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
* with {@link #createTempFileForDownload()}.

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.mailbox;
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.EventBus;
import org.briarproject.bramble.api.event.EventListener;
@@ -10,13 +11,18 @@ import org.briarproject.bramble.api.mailbox.MailboxDirectory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
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.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File;
import java.io.IOException;
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.Executor;
import java.util.logging.Logger;
@@ -30,6 +36,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
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.util.IoUtils.delete;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@@ -41,6 +48,7 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
// Package access for testing
static final String DOWNLOAD_DIR_NAME = "downloads";
static final String UPLOAD_DIR_NAME = "uploads";
private final Executor ioExecutor;
private final PluginManager pluginManager;
@@ -67,14 +75,44 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override
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
try {
orphanLatch.await();
} catch (InterruptedException e) {
throw new IOException(e);
}
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
return File.createTempFile("mailbox", ".tmp", downloadDir);
File dir = createDirectoryIfNeeded(dirName);
return File.createTempFile("mailbox", ".tmp", dir);
}
private File createDirectoryIfNeeded(String name) throws IOException {
@@ -116,6 +154,8 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override
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) {
TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(ID)) {
@@ -127,17 +167,25 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
/**
* This method is called at startup, as soon as the plugin is started, to
* handle any files that were left in the download directory at the last
* shutdown.
* delete any files that were left in the upload directory at the last
* shutdown and handle any files that were left in the download directory.
*/
@IoExecutor
private void handleOrphanedFiles() {
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[] orphans = downloadDir.listFiles();
// Now that we've got the list of orphans, new files can be created
File[] orphanedDownloads = downloadDir.listFiles();
// Now that we've got the list of orphaned downloads, new files
// can be created in the download directory
orphanLatch.countDown();
if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
if (orphanedDownloads != null) {
for (File f : orphanedDownloads) handleDownloadedFile(f);
}
} catch (IOException e) {
logException(LOG, WARNING, e);
}
@@ -165,9 +213,58 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
delegate.dispose(exception, recognised);
if (isHandlingComplete(exception, recognised)) {
LOG.info("Deleting downloaded file");
if (!file.delete()) {
LOG.warning("Failed to delete downloaded file");
}
delete(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,10 +9,12 @@ import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
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.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -98,34 +100,35 @@ class MailboxManagerImpl implements MailboxManager {
@Override
public boolean checkConnection() {
boolean success;
List<MailboxVersion> versions = null;
try {
MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (props == null) throw new DbException();
success = api.checkStatus(props);
versions = api.getServerSupports(props);
} catch (DbException e) {
logException(LOG, WARNING, e);
// we don't treat this is a failure to record
return false;
} catch (IOException | MailboxApi.ApiException e) {
// we record this as a failure
success = false;
logException(LOG, WARNING, e);
}
try {
recordCheckResult(success);
recordCheckResult(versions);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
return success;
return versions != null;
}
private void recordCheckResult(boolean success) throws DbException {
private void recordCheckResult(@Nullable List<MailboxVersion> versions)
throws DbException {
long now = clock.currentTimeMillis();
db.transaction(false, txn -> {
if (success) {
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
if (versions != null) {
mailboxSettingsManager
.recordSuccessfulConnection(txn, now, versions);
} else {
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
}

View File

@@ -52,13 +52,19 @@ public class MailboxModule {
}
@Provides
@Singleton
MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager;
}
@Provides
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
UrlConverter provideUrlConverter(UrlConverterImpl urlConverter) {
return urlConverter;
}
@Provides
MailboxApi provideMailboxApi(MailboxApiImpl mailboxApi) {
return mailboxApi;
}
@@ -114,4 +120,10 @@ public class MailboxModule {
}
return mailboxFileManager;
}
@Provides
MailboxWorkerFactory provideMailboxWorkerFactory(
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
return mailboxWorkerFactory;
}
}

View File

@@ -177,10 +177,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey);
String baseUrl = "http://" + onion + ".onion"; // TODO
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
return new MailboxProperties(onion, setupToken, new ArrayList<>());
}
}

View File

@@ -20,13 +20,13 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@ThreadSafe
@NotNullByDefault
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
@@ -74,16 +74,10 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_ONION, p.getOnion());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
List<MailboxVersion> serverSupports = p.getServerSupports();
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);
encodeServerSupports(serverSupports, s);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
@@ -121,14 +115,30 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
@Override
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
recordSuccessfulConnection(txn, now, null);
}
@Override
public void recordSuccessfulConnection(Transaction txn, long now,
@Nullable List<MailboxVersion> versions) throws DbException {
Settings s = new Settings();
// fetch version that the server supports first
List<MailboxVersion> serverSupports;
if (versions == null) {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
serverSupports = parseServerSupports(oldSettings);
} else {
serverSupports = versions;
// store new versions
encodeServerSupports(serverSupports, s);
}
// now record the successful connection
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
// broadcast status event
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
}
@@ -171,6 +181,17 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
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)
throws DbException {
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();

View File

@@ -242,8 +242,7 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
List<MailboxVersion> serverSupports, String ownOnion)
throws DbException {
String baseUrl = "http://" + ownOnion + ".onion"; // TODO
MailboxProperties properties = new MailboxProperties(baseUrl,
MailboxProperties properties = new MailboxProperties(ownOnion,
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
serverSupports,
new MailboxFolderId(crypto.generateUniqueId().getBytes()),

View File

@@ -0,0 +1,394 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
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.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.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.
*/
private enum State {
CREATED,
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 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,
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.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;
}
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);
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
apiCall = null;
this.file = null;
}
delete(file);
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) {
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();
}
}
}
@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();
}
}

View File

@@ -0,0 +1,27 @@
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);
}

View File

@@ -0,0 +1,81 @@
package org.briarproject.bramble.mailbox;
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.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 MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
@Inject
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
}
@Override
public MailboxWorker createUploadWorker(
ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId) {
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
clock, taskScheduler, eventBus, 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) {
// TODO
throw new UnsupportedOperationException();
}
}

View File

@@ -4,11 +4,13 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
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.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
@@ -55,11 +57,12 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException {
try {
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
List<MailboxVersion> serverSupports =
mailboxApi.getServerSupports(properties);
LOG.info("Own mailbox is reachable");
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordSuccessfulConnection(txn, now));
.recordSuccessfulConnection(txn, now, serverSupports));
// Call the observers and cache the result
onConnectivityCheckSucceeded(now);
return false; // Don't retry

View File

@@ -0,0 +1,369 @@
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
logException(LOG, INFO, e);
}
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

@@ -0,0 +1,17 @@
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

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

View File

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

View File

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

View File

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

View File

@@ -9,14 +9,12 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
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.MAX_LATENCY;
@NotNullByDefault
public class MailboxPluginFactory implements SimplexPluginFactory {
private static final long MAX_LATENCY = DAYS.toMillis(14);
@Inject
MailboxPluginFactory() {
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
@@ -25,7 +26,6 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
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.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
@@ -65,9 +65,9 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
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.WARNING;
import static java.util.logging.Logger.getLogger;
@@ -94,9 +94,7 @@ 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_COUNTRY_BLOCKED;
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.NON_DEFAULT_OBFS4;
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.tryToClose;
@@ -125,45 +123,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
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;
private final Executor wakefulIoExecutor;
private final Executor connectionStatusExecutor;
@@ -172,6 +131,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final SocketFactory torSocketFactory;
private final Clock clock;
private final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto;
private final PluginCallback callback;
private final String architecture;
@@ -191,7 +151,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null;
private volatile String ownOnion = null;
protected abstract int getProcessId();
@@ -206,6 +165,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
@@ -223,6 +183,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto;
this.callback = callback;
this.architecture = architecture;
@@ -277,8 +238,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
// Load the settings
settings = callback.getSettings();
// Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets();
try {
// Install or update the assets if necessary
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())
LOG.warning("Old auth cookie not deleted");
// Start a new Tor process
@@ -340,7 +307,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
info = controlConnection.getInfo("status/circuit-established");
if ("1".equals(info)) {
LOG.info("Tor has already built a circuit");
state.getAndSetCircuitBuilt(true);
state.setCircuitBuilt(true);
}
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
@@ -359,25 +326,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return doneFile.lastModified() > getLastUpdateTime();
}
private void installAssets() throws PluginException {
try {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
// The GeoIP file may exist from a previous installation - we can
// save some space by deleting it.
// TODO: Remove after a reasonable migration period
// (added 2022-03-29)
//noinspection ResultOfMethodCallIgnored
geoIpFile.delete();
installTorExecutable();
installObfs4Executable();
extract(getConfigInputStream(), configFile);
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
} catch (IOException e) {
throw new PluginException(e);
}
private void installAssets() throws IOException {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
// The GeoIP file may exist from a previous installation - we can
// save some space by deleting it.
// TODO: Remove after a reasonable migration period
// (added 2022-03-29)
//noinspection ResultOfMethodCallIgnored
geoIpFile.delete();
installTorExecutable();
installObfs4Executable();
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
}
protected void extract(InputStream in, File dest) throws IOException {
@@ -436,6 +398,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\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
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -512,6 +478,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort));
backoff.reset();
// Accept incoming hidden service connections from Tor
acceptContactConnections(ss);
});
@@ -550,7 +517,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
String onion3 = response.get(HS_ADDRESS);
ownOnion = onion3;
if (LOG.isLoggable(INFO)) {
LOG.info("V3 hidden service " + scrubOnion(onion3));
}
@@ -579,12 +545,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return;
}
LOG.info("Connection received");
backoff.reset();
callback.handleConnection(new TorTransportConnection(this, s));
}
}
protected void enableNetwork(boolean enable) throws IOException {
state.enableNetwork(enable);
if (!state.enableNetwork(enable)) return; // Unchanged
try {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
} catch (TorNotRunningException e) {
@@ -592,28 +559,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
private void enableBridges(List<BridgeType> bridgeTypes)
throws IOException {
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
try {
if (enable) {
if (bridgeTypes.isEmpty()) {
controlConnection.setConf("UseBridges", "0");
controlConnection.resetConf(singletonList("Bridge"));
} else {
Collection<String> conf = new ArrayList<>();
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) {
conf.addAll(circumventionProvider.getBridges(bridgeType));
}
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
}
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
@@ -627,7 +586,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (controlSocket != null && controlConnection != null) {
try {
LOG.info("Stopping Tor");
controlConnection.setConf("DisableNetwork", "1");
controlConnection.shutdownTor("TERM");
controlSocket.close();
} catch (TorNotRunningException e) {
@@ -655,58 +613,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public int 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;
}
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
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;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
}
@@ -715,7 +629,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void connect(TransportProperties p, ConnectionHandler h) {
wakefulIoExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p);
if (d != null) h.handleConnection(d);
if (d != null) {
backoff.reset();
h.handleConnection(d);
}
});
}
@@ -836,8 +753,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void circuitStatus(String status, String id, String path) {
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
// DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
}
@@ -864,9 +782,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (msg.startsWith("Switching to guard context")) {
state.resetNetworkStability();
}
}
@Override
@@ -893,10 +808,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
LOG.info("Bootstrapped");
state.setBootstrapped();
backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (!state.getAndSetCircuitBuilt(true)) LOG.info("Circuit built");
if (state.setCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.getAndSetCircuitBuilt(false)) {
if (state.setCircuitBuilt(false)) {
LOG.info("Circuit not built");
// TODO: Disable and re-enable network to prompt Tor to rebuild
// its guard/bridge connections? This will also close any
@@ -939,15 +858,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void eventOccurred(Event e) {
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) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated");
@@ -964,18 +875,6 @@ 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,
boolean charging) {
connectionStatusExecutor.execute(() -> {
@@ -1007,10 +906,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
int reasonsDisabled = 0;
boolean enableNetwork = false, enableBridges = false;
boolean enableConnectionPadding = false;
List<BridgeType> bridgeTypes =
circumventionProvider.getSuitableBridgeTypes(country);
boolean enableNetwork = false, enableConnectionPadding = false;
List<BridgeType> bridgeTypes = emptyList();
if (!online) {
LOG.info("Disabling network, device is offline");
@@ -1039,8 +936,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (ipv6Only) bridgeTypes = singletonList(MEEK);
enableBridges = true;
if (ipv6Only) {
bridgeTypes = singletonList(MEEK);
} else {
bridgeTypes = circumventionProvider
.getSuitableBridgeTypes(country);
}
if (LOG.isLoggable(INFO)) {
LOG.info("Using bridge types " + bridgeTypes);
}
@@ -1060,9 +961,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try {
if (enableNetwork) {
enableBridges(enableBridges, bridgeTypes);
enableBridges(bridgeTypes);
enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
enableIpv6(ipv6Only);
}
enableNetwork(enableNetwork);
} catch (IOException e) {
@@ -1072,6 +973,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void enableConnectionPadding(boolean enable) throws IOException {
if (!state.enableConnectionPadding(enable)) return; // Unchanged
try {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} catch (TorNotRunningException e) {
@@ -1079,10 +981,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void useIpv6(boolean ipv6Only) throws IOException {
private void enableIpv6(boolean enable) throws IOException {
if (!state.enableIpv6(enable)) return; // Unchanged
try {
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
@@ -1097,6 +1000,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
stopped = false,
networkInitialised = false,
networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
bootstrapped = false,
circuitBuilt = false,
settingsChecked = false;
@@ -1104,13 +1009,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this")
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")
@Nullable
private ServerSocket serverSocket = null;
@@ -1118,6 +1016,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this")
private int orConnectionsConnected = 0;
@GuardedBy("this")
private List<BridgeType> bridgeTypes = emptyList();
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
@@ -1138,28 +1039,66 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private synchronized void setBootstrapped() {
boolean wasBootstrapped = bootstrapped;
bootstrapped = true;
callback.pluginStateChanged(getState());
if (!wasBootstrapped) callback.pluginStateChanged(getState());
}
private synchronized boolean getAndSetCircuitBuilt(boolean built) {
boolean old = circuitBuilt;
/**
* 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;
if (built != old) callback.pluginStateChanged(getState());
return old;
callback.pluginStateChanged(getState());
return true; // Changed
}
private synchronized void enableNetwork(boolean enable) {
/**
* Sets the `networkEnabled` flag and returns true if the flag has
* changed.
*/
private synchronized boolean enableNetwork(boolean enable) {
boolean wasInitialised = networkInitialised;
boolean wasEnabled = networkEnabled;
networkInitialised = true;
networkEnabled = enable;
if (!enable) circuitBuilt = false;
callback.pluginStateChanged(getState());
if (!wasInitialised || enable != wasEnabled) {
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;
this.reasonsDisabled = reasonsDisabled;
callback.pluginStateChanged(getState());
int oldReasons = reasonsDisabled;
reasonsDisabled = reasons;
if (!wasChecked || reasons != oldReasons) {
callback.pluginStateChanged(getState());
}
}
// Doesn't affect getState()
@@ -1174,6 +1113,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
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() {
if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING;
@@ -1205,7 +1155,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) {
resetNetworkStability();
callback.pluginStateChanged(getState());
}
}
@@ -1216,43 +1165,5 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
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,6 +6,8 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
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.TorConstants;
import org.briarproject.bramble.api.plugin.TorControlPort;
@@ -39,12 +41,16 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
protected static final int MAX_LATENCY = 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 NetworkManager networkManager;
protected final LocationUtils locationUtils;
protected final EventBus eventBus;
protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory;
protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager;
@@ -60,6 +66,7 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
@@ -74,6 +81,7 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
@@ -87,7 +95,7 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
@Nullable
abstract String getArchitectureForTorBinary();
abstract TorPlugin createPluginInstance(
abstract TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture);
@@ -114,9 +122,11 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
LOG.info("The selected architecture for Tor is " + architecture);
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto);
TorPlugin plugin = createPluginInstance(torRendezvousCrypto,
TorPlugin plugin = createPluginInstance(backoff, torRendezvousCrypto,
callback, architecture);
eventBus.addListener(plugin);
return plugin;

View File

@@ -50,6 +50,7 @@ import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
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.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_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
@@ -235,8 +236,10 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
generateOffer();
} else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getAffectedContacts().contains(contactId))
if (g.getVisibility() == SHARED &&
g.getAffectedContacts().contains(contactId)) {
generateOffer();
}
} else if (e instanceof MessageRequestedEvent) {
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
generateBatch();
@@ -310,7 +313,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Collection<Message> batch =
db.generateRequestedBatch(txn, contactId,
BATCH_CAPACITY, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
setNextSendTime(db.getNextSendTime(txn, contactId,
maxLatency));
return batch;
});
if (LOG.isLoggable(INFO))
@@ -353,7 +357,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Offer o = db.transactionWithNullableResult(false, txn -> {
Offer offer = db.generateOffer(txn, contactId,
MAX_MESSAGE_IDS, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
setNextSendTime(db.getNextSendTime(txn, contactId,
maxLatency));
return offer;
});
if (LOG.isLoggable(INFO))

View File

@@ -7,9 +7,9 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.DeferredSendHandler;
import org.briarproject.bramble.api.sync.Message;
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.transport.StreamWriter;
@@ -29,7 +29,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
/**
* A {@link SimplexOutgoingSession} for sending and acking messages via a
* mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
* mailbox. The session uses a {@link OutgoingSessionRecord} to record the IDs
* 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
* uploaded to the mailbox.
@@ -41,7 +41,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
private static final Logger LOG =
getLogger(MailboxOutgoingSession.class.getName());
private final DeferredSendHandler deferredSendHandler;
private final OutgoingSessionRecord sessionRecord;
private final long initialCapacity;
MailboxOutgoingSession(DatabaseComponent db,
@@ -51,11 +51,11 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
long maxLatency,
StreamWriter streamWriter,
SyncRecordWriter recordWriter,
DeferredSendHandler deferredSendHandler,
OutgoingSessionRecord sessionRecord,
long capacity) {
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
recordWriter);
this.deferredSendHandler = deferredSendHandler;
this.sessionRecord = sessionRecord;
this.initialCapacity = capacity;
}
@@ -65,7 +65,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
Collection<MessageId> idsToAck = loadMessageIdsToAck();
if (idsToAck.isEmpty()) break;
recordWriter.writeAck(new Ack(idsToAck));
deferredSendHandler.onAckSent(idsToAck);
sessionRecord.onAckSent(idsToAck);
LOG.info("Sent ack");
}
}
@@ -96,7 +96,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
db.getMessageToSend(txn, contactId, m, maxLatency, false));
if (message == null) continue; // No longer shared
recordWriter.writeMessage(message);
deferredSendHandler.onMessageSent(m);
sessionRecord.onMessageSent(m);
LOG.info("Sent message");
}
}

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncRecordReader;
@@ -25,6 +26,8 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_FILE_PAYLOAD_BYTES;
@Immutable
@NotNullByDefault
class SyncSessionFactoryImpl implements SyncSessionFactory {
@@ -73,6 +76,18 @@ 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
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, int maxIdleTime, StreamWriter streamWriter,

View File

@@ -17,17 +17,17 @@ n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert
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 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 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 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 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 87.121.72.109:9002 C8081D4731C953FA4AE166946E72B29153351E34 cert=bikAqxKV6Ch5gFCBTdPI28VeShYa1ZgkLmvc7YZNLWFsFZoaCULL/3AQKjpIfvSiJs5jGQ iat-mode=0
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
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -618,11 +618,12 @@ public class BdfReaderImplTest extends BrambleTestCase {
r.readDictionary();
}
private void setContents(String hex) {
private void setContents(String hex) throws FormatException {
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));
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
}

View File

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

View File

@@ -303,11 +303,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not)
exactly(25).of(database).startTransaction();
exactly(27).of(database).startTransaction();
will(returnValue(txn));
exactly(25).of(database).containsContact(txn, contactId);
exactly(27).of(database).containsContact(txn, contactId);
will(returnValue(false));
exactly(25).of(database).abortTransaction(txn);
exactly(27).of(database).abortTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
@@ -321,6 +321,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// 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 {
db.transaction(false, transaction ->
db.generateAck(transaction, contactId, 123));

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,131 @@
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();
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);
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();
client.destroy();
}
@Test
public void assignContactForDownloadAndDestroyClient() {
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);
client.destroy();
}
@Test
public void assignAndDeassignContactForDownload() {
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();
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();
}});
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
@@ -7,6 +8,7 @@ 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.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -36,6 +38,7 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final MailboxFileManager mailboxFileManager =
context.mock(MailboxFileManager.class);
private final Cancellable apiCall = context.mock(Cancellable.class);
private final MailboxProperties mailboxProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
@@ -96,30 +99,36 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
// When the connectivity check succeeds, a list-inbox task should be
// started for the first download cycle
AtomicReference<ApiCall> listTask = new AtomicReference<>(null);
AtomicReference<ApiCall> listTask = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
will(new DoAllAction(
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onConnectivityCheckSucceeded();
// When the list-inbox tasks runs and finds some files to download,
// it should start a download task for the first file
AtomicReference<ApiCall> downloadTask = new AtomicReference<>(null);
AtomicReference<ApiCall> downloadTask = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(files));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0));
will(new DoAllAction(
new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(listTask.get().callApi());
// When the first download task runs it should download the file to the
// location provided by the file manager and start a delete task
AtomicReference<ApiCall> deleteTask = new AtomicReference<>(null);
AtomicReference<ApiCall> deleteTask = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
@@ -128,7 +137,10 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
file1.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0));
will(new DoAllAction(
new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(downloadTask.get().callApi());
@@ -140,7 +152,10 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
requireNonNull(mailboxProperties.getInboxId()), file1.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0));
will(new DoAllAction(
new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(deleteTask.get().callApi());
@@ -155,7 +170,10 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
file2.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0));
will(new DoAllAction(
new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(downloadTask.get().callApi());
@@ -168,7 +186,10 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
requireNonNull(mailboxProperties.getInboxId()), file2.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
will(new DoAllAction(
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(deleteTask.get().callApi());
@@ -188,7 +209,10 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
// be started for the second download cycle
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new CaptureArgumentAction<>(listTask, ApiCall.class, 0));
will(new DoAllAction(
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onTorReachable();

View File

@@ -68,7 +68,10 @@ public class MailboxApiTest extends BrambleTestCase {
return client;
}
};
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
// We aren't using a real onion address, so use the given address verbatim
private final UrlConverter urlConverter = onion -> onion;
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider,
urlConverter);
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId());

View File

@@ -2,16 +2,20 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
import org.briarproject.bramble.api.plugin.PluginManager;
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.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.ConsumeArgumentAction;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
@@ -20,6 +24,7 @@ import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@@ -28,11 +33,14 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.DOWNLOAD_DIR_NAME;
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.UPLOAD_DIR_NAME;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class MailboxFileManagerImplTest extends BrambleMockTestCase {
@@ -47,6 +55,10 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
private final SimplexPlugin plugin = context.mock(SimplexPlugin.class);
private final TransportConnectionReader transportConnectionReader =
context.mock(TransportConnectionReader.class);
private final TransportConnectionWriter transportConnectionWriter =
context.mock(TransportConnectionWriter.class);
private final ContactId contactId = getContactId();
private File mailboxDir;
private MailboxFileManagerImpl manager;
@@ -65,17 +77,25 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
@Test
public void testHandlesOrphanedFilesAtStartup() throws Exception {
// Create an orphaned file, left behind at the previous shutdown
// Create an orphaned upload, left behind at the previous shutdown
File uploadDir = new File(mailboxDir, UPLOAD_DIR_NAME);
//noinspection ResultOfMethodCallIgnored
uploadDir.mkdirs();
File orphanedUpload = new File(uploadDir, "orphan");
assertTrue(orphanedUpload.createNewFile());
// Create an orphaned download, left behind at the previous shutdown
File downloadDir = new File(mailboxDir, DOWNLOAD_DIR_NAME);
//noinspection ResultOfMethodCallIgnored
downloadDir.mkdirs();
File orphan = new File(downloadDir, "orphan");
assertTrue(orphan.createNewFile());
File orphanedDownload = new File(downloadDir, "orphan");
assertTrue(orphanedDownload.createNewFile());
TransportProperties props = new TransportProperties();
props.put(PROP_PATH, orphan.getAbsolutePath());
props.put(PROP_PATH, orphanedDownload.getAbsolutePath());
// When the plugin becomes active the orphaned file should be handled
// When the plugin becomes active the orphaned upload should be deleted
// and the orphaned download should be handled
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
@@ -90,10 +110,12 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
}});
manager.eventOccurred(new TransportActiveEvent(ID));
assertFalse(orphanedUpload.exists());
}
@Test
public void testDeletesFileWhenReadSucceeds() throws Exception {
public void testDeletesDownloadedFileWhenReadSucceeds() throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
@@ -102,7 +124,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassFileToConnectionManager(f, reader, controller);
expectPassDownloadedFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
// The read is successful, so the tag controller should allow the tag
@@ -117,29 +139,117 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
}
@Test
public void testDeletesFileWhenTagIsNotRecognised() throws Exception {
testDeletesFile(false, RUNNING, false);
}
@Test
public void testDeletesFileWhenReadFails() throws Exception {
testDeletesFile(true, RUNNING, false);
}
@Test
public void testDoesNotDeleteFileWhenTagIsNotRecognisedAtShutdown()
public void testDeletesDownloadedFileWhenTagIsNotRecognised()
throws Exception {
testDeletesFile(false, STOPPING, true);
testDeletesDownloadedFile(false, RUNNING, false);
}
@Test
public void testDoesNotDeleteFileWhenReadFailsAtShutdown()
throws Exception {
testDeletesFile(true, STOPPING, true);
public void testDeletesDownloadedFileWhenReadFails() throws Exception {
testDeletesDownloadedFile(true, RUNNING, false);
}
private void testDeletesFile(boolean recognised, LifecycleState state,
boolean fileExists) throws Exception {
@Test
public void testDoesNotDeleteDownloadedFileWhenTagIsNotRecognisedAtShutdown()
throws Exception {
testDeletesDownloadedFile(false, STOPPING, true);
}
@Test
public void testDoesNotDeleteDownloadedFileWhenReadFailsAtShutdown()
throws Exception {
testDeletesDownloadedFile(true, STOPPING, true);
}
@Test(expected = IOException.class)
public void testThrowsExceptionIfPluginFailsToCreateWriter()
throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
will(returnValue(null));
}});
manager.createAndWriteTempFileForUpload(contactId, sessionRecord);
}
@Test(expected = IOException.class)
public void testThrowsExceptionIfSessionFailsWithException()
throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
will(returnValue(transportConnectionWriter));
oneOf(transportConnectionWriter).dispose(true);
oneOf(connectionManager).manageOutgoingConnection(with(contactId),
with(ID), with(any(TransportConnectionWriter.class)),
with(sessionRecord));
// The session fails with an exception. We need to use an action
// for this, as createAndWriteTempFileForUpload() waits for it to
// happen before returning
will(new ConsumeArgumentAction<>(TransportConnectionWriter.class, 2,
writer -> {
try {
writer.dispose(true);
} catch (IOException e) {
fail();
}
}
));
}});
manager.createAndWriteTempFileForUpload(contactId, sessionRecord);
}
@Test
public void testReturnsFileIfSessionSucceeds() throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
will(returnValue(transportConnectionWriter));
oneOf(transportConnectionWriter).dispose(false);
oneOf(connectionManager).manageOutgoingConnection(with(contactId),
with(ID), with(any(TransportConnectionWriter.class)),
with(sessionRecord));
// The session succeeds. We need to use an action for this, as
// createAndWriteTempFileForUpload() waits for it to happen before
// returning
will(new ConsumeArgumentAction<>(TransportConnectionWriter.class, 2,
writer -> {
try {
writer.dispose(false);
} catch (IOException e) {
fail();
}
}
));
}});
File f = manager.createAndWriteTempFileForUpload(contactId,
sessionRecord);
assertTrue(f.exists());
}
private void testDeletesDownloadedFile(boolean recognised,
LifecycleState state, boolean fileExists) throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
@@ -148,7 +258,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassFileToConnectionManager(f, reader, controller);
expectPassDownloadedFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
context.checking(new Expectations() {{
@@ -169,7 +279,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
}});
}
private void expectPassFileToConnectionManager(File f,
private void expectPassDownloadedFileToConnectionManager(File f,
AtomicReference<TransportConnectionReader> reader,
AtomicReference<TagController> controller) {
TransportProperties props = new TransportProperties();

View File

@@ -49,8 +49,8 @@ public class MailboxIntegrationTest extends BrambleTestCase {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private final static String URL_BASE = "http://127.0.0.1:8000";
private final static MailboxAuthToken SETUP_TOKEN;
private static final String URL_BASE = "http://127.0.0.1:8000";
private static final MailboxAuthToken SETUP_TOKEN;
static {
try {
@@ -74,8 +74,10 @@ public class MailboxIntegrationTest extends BrambleTestCase {
return client;
}
};
private final static MailboxApiImpl api =
new MailboxApiImpl(httpClientProvider);
// We aren't using a real onion address, so use the given address verbatim
private static final UrlConverter urlConverter = onion -> onion;
private static final MailboxApiImpl api =
new MailboxApiImpl(httpClientProvider, urlConverter);
// needs to be static to keep values across different tests
private static MailboxProperties ownerProperties;
@@ -121,7 +123,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
ContactId contactId = new ContactId(1);
MailboxContact contact = getMailboxContact(contactId);
MailboxProperties contactProperties = new MailboxProperties(
ownerProperties.getBaseUrl(), contact.token,
ownerProperties.getOnion(), contact.token,
new ArrayList<>(), contact.inboxId, contact.outboxId);
api.addContact(ownerProperties, contact);
@@ -167,7 +169,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
ContactId contactId = new ContactId(1);
MailboxContact contact = getMailboxContact(contactId);
MailboxProperties contactProperties = new MailboxProperties(
ownerProperties.getBaseUrl(), contact.token,
ownerProperties.getOnion(), contact.token,
new ArrayList<>(), contact.inboxId, contact.outboxId);
api.addContact(ownerProperties, contact);

View File

@@ -0,0 +1,128 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.junit.Test;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Executor;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.hasEvent;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MailboxManagerImplTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final MailboxApi api = context.mock(MailboxApi.class);
private final TransactionManager db =
context.mock(TransactionManager.class);
private final MailboxSettingsManager mailboxSettingsManager =
context.mock(MailboxSettingsManager.class);
private final MailboxPairingTaskFactory pairingTaskFactory =
context.mock(MailboxPairingTaskFactory.class);
private final Clock clock =
context.mock(Clock.class);
private final MailboxManagerImpl manager = new MailboxManagerImpl(
ioExecutor, api, db, mailboxSettingsManager, pairingTaskFactory,
clock);
@Test
public void testDbExceptionDoesNotRecordFailure() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(throwException(new DbException()));
}});
assertFalse(manager.checkConnection());
assertFalse(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
}
@Test
public void testIOExceptionDoesRecordFailure() throws Exception {
Transaction txn = new Transaction(null, true);
Transaction txn2 = new Transaction(null, false);
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
long now = new Random().nextLong();
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(props));
oneOf(api).getServerSupports(props);
will(throwException(new IOException()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn2));
oneOf(mailboxSettingsManager)
.recordFailedConnectionAttempt(txn2, now);
}});
assertFalse(manager.checkConnection());
}
@Test
public void testApiExceptionDoesRecordFailure() throws Exception {
Transaction txn = new Transaction(null, true);
Transaction txn2 = new Transaction(null, false);
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
long now = new Random().nextLong();
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(props));
oneOf(api).getServerSupports(props);
will(throwException(new MailboxApi.ApiException()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn2));
oneOf(mailboxSettingsManager)
.recordFailedConnectionAttempt(txn2, now);
}});
assertFalse(manager.checkConnection());
}
@Test
public void testConnectionSuccess() throws Exception {
Transaction txn = new Transaction(null, true);
Transaction txn2 = new Transaction(null, false);
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
long now = new Random().nextLong();
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(props));
oneOf(api).getServerSupports(props);
will(returnValue(CLIENT_SUPPORTS));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn2));
oneOf(mailboxSettingsManager)
.recordSuccessfulConnection(txn2, now, CLIENT_SUPPORTS);
}});
assertTrue(manager.checkConnection());
}
}

View File

@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
@@ -33,7 +32,6 @@ import static java.util.Collections.singletonList;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.hasEvent;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -57,7 +55,6 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
private final String onion = getRandomString(56);
private final byte[] onionBytes = getRandomBytes(32);
private final String baseUrl = "http://" + onion + ".onion"; // TODO
private final MailboxAuthToken setupToken =
new MailboxAuthToken(getRandomId());
private final MailboxAuthToken ownerToken =
@@ -65,9 +62,9 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
private final String validPayload = getValidPayload();
private final long time = System.currentTimeMillis();
private final MailboxProperties setupProperties = new MailboxProperties(
baseUrl, setupToken, new ArrayList<>());
onion, setupToken, new ArrayList<>());
private final MailboxProperties ownerProperties = new MailboxProperties(
baseUrl, ownerToken, new ArrayList<>());
onion, ownerToken, new ArrayList<>());
@Test
public void testInitialQrCodeReceivedState() {
@@ -138,7 +135,6 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
i.getAndIncrement();
});
task.run();
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
}
@Test
@@ -210,7 +206,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
p1.getAuthToken().equals(p2.getAuthToken()) &&
p1.getBaseUrl().equals(p2.getBaseUrl()) &&
p1.getOnion().equals(p2.getOnion()) &&
p1.isOwner() == p2.isOwner() &&
p1.getServerSupports().equals(p2.getServerSupports()));
}

View File

@@ -18,6 +18,7 @@ import java.util.List;
import java.util.Random;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
@@ -83,7 +84,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
assertNotNull(properties);
assertEquals(onion, properties.getBaseUrl());
assertEquals(onion, properties.getOnion());
assertEquals(token, properties.getAuthToken());
assertEquals(serverSupports, properties.getServerSupports());
assertTrue(properties.isOwner());
@@ -162,6 +163,28 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
}});
manager.recordSuccessfulConnection(txn, now);
assertTrue(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
}
@Test
public void testRecordsSuccessWithVersions() throws Exception {
Transaction txn = new Transaction(null, false);
List<MailboxVersion> versions = singletonList(new MailboxVersion(2, 1));
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
expectedSettings.putInt(SETTINGS_KEY_SERVER_SUPPORTS, 0);
int[] newVersionsInts = {2, 1};
expectedSettings
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, newVersionsInts);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.recordSuccessfulConnection(txn, now, versions);
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
}

View File

@@ -109,7 +109,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
updateNoMailbox = new MailboxUpdate(someClientSupportsList);
updateProps = getMailboxProperties(false, someServerSupportsList);
ownProps = new MailboxProperties(updateProps.getBaseUrl(),
ownProps = new MailboxProperties(updateProps.getOnion(),
updateProps.getAuthToken(), someServerSupportsList);
updateWithMailbox = new MailboxUpdateWithMailbox(someClientSupportsList,
updateProps);
@@ -170,7 +170,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict, true);
emptyServerSupports, emptyPropsDict);
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
sentDict);
@@ -225,7 +225,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
someServerSupports, propsDict, true);
someServerSupports, propsDict);
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
sentDict);
@@ -276,7 +276,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(emptyMessageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict, true);
emptyServerSupports, emptyPropsDict);
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
sentDict);
@@ -341,7 +341,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(emptyMessageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict, true);
emptyServerSupports, emptyPropsDict);
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
sentDict);
@@ -400,7 +400,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 2,
newerClientSupports, someServerSupports, propsDict, true);
newerClientSupports, someServerSupports, propsDict);
oneOf(db).removeMessage(txn, messageId);
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
@@ -441,7 +441,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict, true);
emptyServerSupports, emptyPropsDict);
}});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
@@ -484,7 +484,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
someServerSupports, propsDict, true);
someServerSupports, propsDict);
}});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
@@ -674,7 +674,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
someServerSupports, propsDict, true);
someServerSupports, propsDict);
oneOf(db).removeMessage(txn, latestId);
}});
@@ -712,7 +712,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contactGroup.getId());
will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
emptyServerSupports, emptyPropsDict, true);
emptyServerSupports, emptyPropsDict);
oneOf(db).removeMessage(txn, latestId);
}});
@@ -890,15 +890,14 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
private void expectStoreMessage(Transaction txn, GroupId g,
long version, BdfList clientSupports, BdfList serverSupports,
BdfDictionary properties, boolean local)
throws Exception {
BdfDictionary properties) throws Exception {
BdfList body = BdfList.of(version, clientSupports, serverSupports,
properties);
Message message = getMessage(g);
long timestamp = message.getTimestamp();
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, version),
new BdfEntry(MSG_KEY_LOCAL, local)
new BdfEntry(MSG_KEY_LOCAL, true)
);
context.checking(new Expectations() {{

View File

@@ -0,0 +1,465 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.ConsumeArgumentAction;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
import static org.briarproject.bramble.mailbox.MailboxUploadWorker.CHECK_DELAY_MS;
import static org.briarproject.bramble.mailbox.MailboxUploadWorker.RETRY_DELAY_MS;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getContactId;
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.assertTrue;
public class MailboxUploadWorkerTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final Clock clock = context.mock(Clock.class);
private final TaskScheduler taskScheduler =
context.mock(TaskScheduler.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.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 Cancellable apiCall =
context.mock(Cancellable.class, "apiCall");
private final Cancellable wakeupTask =
context.mock(Cancellable.class, "wakeupTask");
private final Cancellable checkTask =
context.mock(Cancellable.class, "checkTask");
private final MailboxProperties mailboxProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
private final MailboxFolderId folderId = new MailboxFolderId(getRandomId());
private final ContactId contactId = getContactId();
private final MessageId ackedId = new MessageId(getRandomId());
private final MessageId sentId = new MessageId(getRandomId());
private final MessageId newMessageId = new MessageId(getRandomId());
private File testDir, tempFile;
private MailboxUploadWorker worker;
@Before
public void setUp() {
testDir = getTestDirectory();
tempFile = new File(testDir, "temp");
worker = new MailboxUploadWorker(ioExecutor, db, clock, taskScheduler,
eventBus, connectivityChecker, mailboxApiCaller, mailboxApi,
mailboxFileManager, mailboxProperties, folderId, contactId);
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
@Test
public void testChecksForDataWhenStartedAndRemovesObserverWhenDestroyed()
throws Exception {
// When the worker is started it should check for data to send
expectRunTaskOnIoExecutor();
expectCheckForDataToSendNoDataWaiting();
worker.start();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testChecksConnectivityWhenStartedIfDataIsReady()
throws Exception {
Transaction recordTxn = new Transaction(null, false);
// When the worker is started it should check for data to send. As
// there's data ready to send immediately, the worker should start a
// connectivity check
expectRunTaskOnIoExecutor();
expectCheckForDataToSendAndStartConnectivityCheck();
worker.start();
// Create the temporary file so we can test that it gets deleted
assertTrue(testDir.mkdirs());
assertTrue(tempFile.createNewFile());
// When the connectivity check succeeds, the worker should write a file
// and start an upload task
expectRunTaskOnIoExecutor();
AtomicReference<ApiCall> upload = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
with(contactId), with(any(OutgoingSessionRecord.class)));
will(new DoAllAction(
// Record some IDs as acked and sent
new ConsumeArgumentAction<>(OutgoingSessionRecord.class, 1,
record -> {
record.onAckSent(singletonList(ackedId));
record.onMessageSent(sentId);
}),
returnValue(tempFile)
));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(upload, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onConnectivityCheckSucceeded();
// When the upload task runs, it should upload the file, record
// the acked/sent messages in the DB, and check for more data to send
context.checking(new DbExpectations() {{
oneOf(mailboxApi).addFile(mailboxProperties, folderId, tempFile);
oneOf(db).transaction(with(false), withDbRunnable(recordTxn));
oneOf(db).setAckSent(recordTxn, contactId, singletonList(ackedId));
oneOf(db).setMessagesSent(recordTxn, contactId,
singletonList(sentId), MAX_LATENCY);
}});
expectCheckForDataToSendNoDataWaiting();
assertFalse(upload.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
// The file should have been deleted
assertFalse(tempFile.exists());
}
@Test
public void testCancelsApiCallWhenDestroyed() throws Exception {
// When the worker is started it should check for data to send. As
// there's data ready to send immediately, the worker should start a
// connectivity check
expectRunTaskOnIoExecutor();
expectCheckForDataToSendAndStartConnectivityCheck();
worker.start();
// Create the temporary file so we can test that it gets deleted
assertTrue(testDir.mkdirs());
assertTrue(tempFile.createNewFile());
// When the connectivity check succeeds, the worker should write a file
// and start an upload task
expectRunTaskOnIoExecutor();
AtomicReference<ApiCall> upload = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
with(contactId), with(any(OutgoingSessionRecord.class)));
will(new DoAllAction(
// Record some IDs as acked and sent
new ConsumeArgumentAction<>(OutgoingSessionRecord.class, 1,
record -> {
record.onAckSent(singletonList(ackedId));
record.onMessageSent(sentId);
}),
returnValue(tempFile)
));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(upload, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onConnectivityCheckSucceeded();
// When the worker is destroyed it should remove the connectivity
// observer and event listener and cancel the upload task
context.checking(new Expectations() {{
oneOf(apiCall).cancel();
}});
expectRemoveObserverAndListener();
worker.destroy();
// The file should have been deleted
assertFalse(tempFile.exists());
// If the upload task runs anyway (cancellation came too late), it
// should return early when it finds the state has changed
assertFalse(upload.get().callApi());
}
@Test
public void testSchedulesWakeupWhenStartedIfDataIsNotReady()
throws Exception {
// When the worker is started it should check for data to send. As
// the data isn't ready to send immediately, the worker should
// schedule a wakeup
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start();
// When the wakeup task runs it should check for data to send
expectCheckForDataToSendNoDataWaiting();
wakeup.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testCancelsWakeupIfDestroyedBeforeWakingUp() throws Exception {
// When the worker is started it should check for data to send. As
// the data isn't ready to send immediately, the worker should
// schedule a wakeup
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start();
// When the worker is destroyed it should cancel the wakeup and
// remove the connectivity observer and event listener
context.checking(new Expectations() {{
oneOf(wakeupTask).cancel();
}});
expectRemoveObserverAndListener();
worker.destroy();
// If the wakeup task runs anyway (cancellation came too late), it
// should return early without doing anything
wakeup.get().run();
}
@Test
public void testCancelsWakeupIfEventIsReceivedBeforeWakingUp()
throws Exception {
// When the worker is started it should check for data to send. As
// the data isn't ready to send immediately, the worker should
// schedule a wakeup
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start();
// Before the wakeup task runs, the worker receives an event that
// indicates new data may be available. The worker should cancel the
// wakeup task and schedule a check for new data after a short delay
AtomicReference<Runnable> check = new AtomicReference<>();
expectScheduleCheck(check, CHECK_DELAY_MS);
context.checking(new Expectations() {{
oneOf(wakeupTask).cancel();
}});
worker.eventOccurred(new MessageSharedEvent(newMessageId));
// If the wakeup task runs anyway (cancellation came too late), it
// should return early when it finds the state has changed
wakeup.get().run();
// Before the check task runs, the worker receives another event that
// indicates new data may be available. The event should be ignored,
// as a check for new data has already been scheduled
worker.eventOccurred(new MessageSharedEvent(newMessageId));
// When the check task runs, it should check for new data
expectCheckForDataToSendNoDataWaiting();
check.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testCancelsCheckWhenDestroyed() throws Exception {
// When the worker is started it should check for data to send
expectRunTaskOnIoExecutor();
expectCheckForDataToSendNoDataWaiting();
worker.start();
// The worker receives an event that indicates new data may be
// available. The worker should schedule a check for new data after
// a short delay
AtomicReference<Runnable> check = new AtomicReference<>();
expectScheduleCheck(check, CHECK_DELAY_MS);
worker.eventOccurred(new MessageSharedEvent(newMessageId));
// When the worker is destroyed it should cancel the check and
// remove the connectivity observer and event listener
context.checking(new Expectations() {{
oneOf(checkTask).cancel();
}});
expectRemoveObserverAndListener();
worker.destroy();
// If the check runs anyway (cancellation came too late), it should
// return early when it finds the state has changed
check.get().run();
}
@Test
public void testRetriesAfterDelayIfExceptionOccursWhileWritingFile()
throws Exception {
// When the worker is started it should check for data to send. As
// there's data ready to send immediately, the worker should start a
// connectivity check
expectRunTaskOnIoExecutor();
expectCheckForDataToSendAndStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should try to
// write a file. This fails with an exception, so the worker should
// retry by scheduling a check for new data after a short delay
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> check = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
with(contactId), with(any(OutgoingSessionRecord.class)));
will(throwException(new IOException())); // Oh noes!
}});
expectScheduleCheck(check, RETRY_DELAY_MS);
worker.onConnectivityCheckSucceeded();
// When the check task runs it should check for new data
expectCheckForDataToSendNoDataWaiting();
check.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
private void expectRunTaskOnIoExecutor() {
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
}});
}
private void expectCheckForDataToSendNoDataWaiting() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).containsAcksToSend(txn, contactId);
will(returnValue(false));
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
will(returnValue(Long.MAX_VALUE)); // No data waiting
}});
}
private void expectCheckForDataToSendAndScheduleWakeup(
AtomicReference<Runnable> wakeup) throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).containsAcksToSend(txn, contactId);
will(returnValue(false));
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
will(returnValue(now + 1234L)); // Data waiting but not ready
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(1234L), with(MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(wakeup, Runnable.class, 0),
returnValue(wakeupTask)
));
}});
}
private void expectCheckForDataToSendAndStartConnectivityCheck()
throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).containsAcksToSend(txn, contactId);
will(returnValue(false));
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
will(returnValue(0L)); // Data ready to send
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(connectivityChecker).checkConnectivity(mailboxProperties,
worker);
}});
}
private void expectScheduleCheck(AtomicReference<Runnable> check,
long delay) {
context.checking(new Expectations() {{
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(delay), with(MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(check, Runnable.class, 0),
returnValue(checkTask)
));
}});
}
private void expectRemoveObserverAndListener() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(eventBus).removeListener(worker);
}});
}
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.test.BrambleMockTestCase;
@@ -15,8 +16,10 @@ import org.jmock.lib.action.DoAllAction;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertFalse;
@@ -39,6 +42,8 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
private final MailboxProperties properties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
private final List<MailboxVersion> serverSupports =
singletonList(new MailboxVersion(123, 456));
@Test
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
@@ -62,12 +67,13 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
// When the check succeeds, the success should be recorded in the DB
// and the observer should be called
context.checking(new DbExpectations() {{
oneOf(mailboxApi).checkStatus(properties);
will(returnValue(true));
oneOf(mailboxApi).getServerSupports(properties);
will(returnValue(serverSupports));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now);
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now,
serverSupports);
oneOf(observer).onConnectivityCheckSucceeded();
}});
@@ -97,7 +103,7 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
// When the check fails, the failure should be recorded in the DB and
// the observer should not be called
context.checking(new DbExpectations() {{
oneOf(mailboxApi).checkStatus(properties);
oneOf(mailboxApi).getServerSupports(properties);
will(throwException(new IOException()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));

View File

@@ -0,0 +1,406 @@
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.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
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.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
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.getContact;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class OwnMailboxContactListWorkerTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class);
private final Cancellable apiCall = context.mock(Cancellable.class);
private final MailboxProperties mailboxProperties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final Contact contact1 = getContact(), contact2 = getContact();
private final MailboxProperties contactProperties1 =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxProperties contactProperties2 =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxUpdateWithMailbox update1 =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, contactProperties1);
private final MailboxUpdateWithMailbox update2 =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, contactProperties2);
private final OwnMailboxContactListWorker worker =
new OwnMailboxContactListWorker(ioExecutor, db, eventBus,
connectivityChecker, mailboxApiCaller, mailboxApi,
mailboxUpdateManager, mailboxProperties);
@Test
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testUpdatesContactListWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// When the fetch task runs it should fetch the remote contact list,
// load the local contact list, and find the differences. Contact 2
// needs to be added and contact 1 needs to be removed. The worker
// should load the mailbox update for contact 2 and start a task to
// add contact 2 to the mailbox
expectFetchRemoteContactList(singletonList(contact1.getId()));
expectRunTaskOnIoExecutor();
expectLoadLocalContactList(singletonList(contact2));
expectRunTaskOnIoExecutor();
expectLoadMailboxUpdate(contact2, update2);
AtomicReference<ApiCall> addContact = new AtomicReference<>();
expectStartTaskToAddContact(addContact);
assertFalse(fetchList.get().callApi());
// When the add-contact task runs it should add contact 2 to the
// mailbox, then continue with the next update
AtomicReference<MailboxContact> added = new AtomicReference<>();
expectAddContactToMailbox(added);
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
expectStartTaskToRemoveContact(removeContact);
assertFalse(addContact.get().callApi());
// Check that the added contact has the expected properties
MailboxContact expected = new MailboxContact(contact2.getId(),
contactProperties2.getAuthToken(),
requireNonNull(contactProperties2.getInboxId()),
requireNonNull(contactProperties2.getOutboxId()));
assertMailboxContactEquals(expected, added.get());
// When the remove-contact task runs it should remove contact 1 from
// the mailbox
expectRemoveContactFromMailbox(contact1);
assertFalse(removeContact.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testHandlesEventsAfterMakingInitialUpdates() throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// When the fetch task runs it should fetch the remote contact list,
// load the local contact list, and find the differences. The lists
// are the same, so the worker should wait for changes
expectFetchRemoteContactList(emptyList());
expectRunTaskOnIoExecutor();
expectLoadLocalContactList(emptyList());
assertFalse(fetchList.get().callApi());
// When a contact is added, the worker should load the contact's
// mailbox update and start a task to add the contact to the mailbox
expectRunTaskOnIoExecutor();
expectLoadMailboxUpdate(contact1, update1);
AtomicReference<ApiCall> addContact = new AtomicReference<>();
expectStartTaskToAddContact(addContact);
worker.eventOccurred(new ContactAddedEvent(contact1.getId(), true));
// When the add-contact task runs it should add contact 1 to the
// mailbox
AtomicReference<MailboxContact> added = new AtomicReference<>();
expectAddContactToMailbox(added);
assertFalse(addContact.get().callApi());
// Check that the added contact has the expected properties
MailboxContact expected = new MailboxContact(contact1.getId(),
contactProperties1.getAuthToken(),
requireNonNull(contactProperties1.getInboxId()),
requireNonNull(contactProperties1.getOutboxId()));
assertMailboxContactEquals(expected, added.get());
// When the contact is removed again, the worker should start a task
// to remove the contact from the mailbox
expectRunTaskOnIoExecutor();
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
expectStartTaskToRemoveContact(removeContact);
worker.eventOccurred(new ContactRemovedEvent(contact1.getId()));
// When the remove-contact task runs it should remove the contact from
// the mailbox
expectRemoveContactFromMailbox(contact1);
assertFalse(removeContact.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testHandlesNoSuchContactException() throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// When the fetch task runs it should fetch the remote contact list,
// load the local contact list, and find the differences. Contact 1
// needs to be added, so the worker should submit a task to the
// IO executor to load the contact's mailbox update
expectFetchRemoteContactList(emptyList());
expectRunTaskOnIoExecutor();
expectLoadLocalContactList(singletonList(contact1));
AtomicReference<Runnable> loadUpdate = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new CaptureArgumentAction<>(loadUpdate, Runnable.class, 0));
}});
assertFalse(fetchList.get().callApi());
// Before the contact's mailbox update can be loaded, the contact
// is removed
worker.eventOccurred(new ContactRemovedEvent(contact1.getId()));
// When the load-update task runs, a NoSuchContactException is thrown.
// The worker should abandon adding the contact and move on to the
// next update, which is the removal of the same contact. The worker
// should start a task to remove the contact from the mailbox
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(mailboxUpdateManager).getLocalUpdate(txn, contact1.getId());
will(throwException(new NoSuchContactException()));
}});
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
expectStartTaskToRemoveContact(removeContact);
loadUpdate.get().run();
// When the remove-contact task runs it should remove the contact from
// the mailbox. The contact was never added, so the call throws a
// TolerableFailureException
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteContact(mailboxProperties,
contact1.getId());
will(throwException(new TolerableFailureException()));
}});
assertFalse(removeContact.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testCancelsApiCallWhenDestroyed() {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// The worker is destroyed before the task runs. The worker should
// cancel the task remove the connectivity observer and event listener
context.checking(new Expectations() {{
oneOf(apiCall).cancel();
}});
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
private void expectStartConnectivityCheck() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
}
private void expectRemoveConnectivityObserverAndEventListener() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(eventBus).removeListener(worker);
}});
}
private void expectStartTaskToFetchRemoteContactList(
AtomicReference<ApiCall> task) {
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
private void expectFetchRemoteContactList(List<ContactId> remote)
throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).getContacts(mailboxProperties);
will(returnValue(remote));
}});
}
private void expectLoadLocalContactList(List<Contact> local)
throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).getContacts(txn);
will(returnValue(local));
}});
}
private void expectLoadMailboxUpdate(Contact c, MailboxUpdate update)
throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true),
withDbCallable(txn));
oneOf(mailboxUpdateManager).getLocalUpdate(txn, c.getId());
will(returnValue(update));
}});
}
private void expectStartTaskToAddContact(AtomicReference<ApiCall> task) {
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
private void expectAddContactToMailbox(
AtomicReference<MailboxContact> added) throws Exception {
context.checking(new DbExpectations() {{
oneOf(mailboxApi).addContact(with(mailboxProperties),
with(any(MailboxContact.class)));
will(new CaptureArgumentAction<>(added, MailboxContact.class, 1));
}});
}
private void expectStartTaskToRemoveContact(AtomicReference<ApiCall> task) {
context.checking(new DbExpectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
private void expectRemoveContactFromMailbox(Contact c) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteContact(mailboxProperties, c.getId());
}});
}
private void expectRunTaskOnIoExecutor() {
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
}});
}
private void assertMailboxContactEquals(MailboxContact expected,
MailboxContact actual) {
assertEquals(expected.contactId, actual.contactId);
assertEquals(expected.token, actual.token);
assertEquals(expected.inboxId, actual.inboxId);
assertEquals(expected.outboxId, actual.outboxId);
}
}

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.Executor;
@@ -38,13 +37,9 @@ public class TorReachabilityMonitorImplTest extends BrambleMockTestCase {
private final TorReachabilityObserver observer =
context.mock(TorReachabilityObserver.class);
private TorReachabilityMonitorImpl monitor;
@Before
public void setUp() {
monitor = new TorReachabilityMonitorImpl(ioExecutor, taskScheduler,
pluginManager, eventBus);
}
private final TorReachabilityMonitorImpl monitor =
new TorReachabilityMonitorImpl(ioExecutor, taskScheduler,
pluginManager, eventBus);
@Test
public void testSchedulesTaskWhenStartedIfTorIsActive() {

View File

@@ -1,69 +1,45 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class BackoffImplTest extends BrambleMockTestCase {
public class BackoffImplTest extends BrambleTestCase {
private final EventBus eventBus = context.mock(EventBus.class);
private final TransportId transportId = getTransportId();
private static final int MIN_INTERVAL = 60 * 1000;
private static final int MAX_INTERVAL = 60 * 60 * 1000;
private static final double BASE = 1.2;
@Test
public void testPollingIntervalStartsAtMinimum() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testIncrementMethodIncreasesPollingInterval() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
public void testIncrementIncreasesPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
assertTrue(b.getPollingInterval() > MIN_INTERVAL);
}
@Test
public void testResetMethodResetsPollingInterval() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
public void testResetResetsPollingInterval() {
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
b.increment();
assertTrue(b.getPollingInterval() > MIN_INTERVAL);
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(
PollingIntervalDecreasedEvent.class)));
}});
b.reset();
assertEquals(MIN_INTERVAL, b.getPollingInterval());
context.assertIsSatisfied();
// Resetting again should not broadcast another event
b.reset();
assertEquals(MIN_INTERVAL, b.getPollingInterval());
}
@Test
public void testBaseAffectsBackoffSpeed() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
b.increment();
int interval = b.getPollingInterval();
BackoffImpl b1 = new BackoffImpl(eventBus, transportId, MIN_INTERVAL,
MAX_INTERVAL, BASE * 2);
BackoffImpl b1 = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE * 2);
b1.increment();
int interval1 = b1.getPollingInterval();
assertTrue(interval < interval1);
@@ -71,16 +47,15 @@ public class BackoffImplTest extends BrambleMockTestCase {
@Test
public void testIntervalDoesNotExceedMaxInterval() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, BASE);
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL, BASE);
for (int i = 0; i < 100; i++) b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}
@Test
public void testIntervalDoesNotExceedMaxIntervalWithInfiniteMultiplier() {
BackoffImpl b = new BackoffImpl(eventBus, transportId,
MIN_INTERVAL, MAX_INTERVAL, Double.POSITIVE_INFINITY);
BackoffImpl b = new BackoffImpl(MIN_INTERVAL, MAX_INTERVAL,
Double.POSITIVE_INFINITY);
b.increment();
assertEquals(MAX_INTERVAL, b.getPollingInterval());
}

View File

@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.PollingIntervalDecreasedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
@@ -26,7 +26,6 @@ import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import java.security.SecureRandom;
@@ -35,6 +34,7 @@ import java.util.concurrent.Executor;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.CollectionMatcher.collectionOf;
@@ -58,22 +58,18 @@ public class PollerImplTest extends BrambleMockTestCase {
private final SecureRandom random;
private final Executor ioExecutor = new ImmediateExecutor();
private final Executor wakefulIoExecutor = new ImmediateExecutor();
private final TransportId transportId = getTransportId();
private final ContactId contactId = getContactId();
private final TransportProperties properties = new TransportProperties();
private final int pollingInterval = 60 * 1000;
private final long now = System.currentTimeMillis();
private PollerImpl poller;
private final PollerImpl poller;
public PollerImplTest() {
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
random = context.mock(SecureRandom.class);
}
@Before
public void setUp() {
Executor wakefulIoExecutor = new ImmediateExecutor();
poller = new PollerImpl(ioExecutor, wakefulIoExecutor, scheduler,
connectionManager, connectionRegistry, pluginManager,
transportPropertyManager, random, clock);
@@ -216,7 +212,7 @@ public class PollerImplTest extends BrambleMockTestCase {
}
@Test
public void testRescheduleOnPollingIntervalDecreased() {
public void testRescheduleOnConnectionOpened() {
Plugin plugin = context.mock(Plugin.class);
context.checking(new Expectations() {{
@@ -239,7 +235,8 @@ public class PollerImplTest extends BrambleMockTestCase {
will(returnValue(cancellable));
}});
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
}
@Test
@@ -279,8 +276,10 @@ public class PollerImplTest extends BrambleMockTestCase {
will(returnValue(now + 1));
}});
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
}
@Test
@@ -324,8 +323,10 @@ public class PollerImplTest extends BrambleMockTestCase {
with(MILLISECONDS));
}});
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
poller.eventOccurred(new PollingIntervalDecreasedEvent(transportId));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
}
@Test
@@ -372,6 +373,48 @@ public class PollerImplTest extends BrambleMockTestCase {
poller.eventOccurred(new TransportActiveEvent(transportId));
}
@Test
public void testDoesNotPollIfAllContactsAreConnected() throws Exception {
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule a polling task immediately
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(0L), with(MILLISECONDS));
will(returnValue(cancellable));
will(new RunAction());
// Running the polling task schedules the next polling task
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(random).nextDouble();
will(returnValue(0.5));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with((long) (pollingInterval * 0.5)),
with(MILLISECONDS));
will(returnValue(cancellable));
// Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId);
will(returnValue(singletonMap(contactId, properties)));
oneOf(connectionRegistry).getConnectedOrBetterContacts(transportId);
will(returnValue(singletonList(contactId)));
// All contacts are connected, so don't poll the plugin
}});
poller.eventOccurred(new TransportActiveEvent(transportId));
}
@Test
public void testCancelsPollingOnTransportDeactivated() {
Plugin plugin = context.mock(Plugin.class);

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@@ -46,13 +45,11 @@ public class LanTcpPluginTest extends BrambleTestCase {
private final Backoff backoff = new TestBackoff();
private final ExecutorService ioExecutor = newCachedThreadPool();
private final Callback callback = new Callback();
private Callback callback = null;
private LanTcpPlugin plugin = null;
private final LanTcpPlugin plugin;
@Before
public void setUp() {
callback = new Callback();
public LanTcpPluginTest() {
plugin = new LanTcpPlugin(ioExecutor, ioExecutor, backoff, callback,
0, 0, 1000) {
@Override
@@ -342,10 +339,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
public void pluginStateChanged(State newState) {
}
@Override
public void pollingIntervalDecreased() {
}
@Override
public void handleConnection(DuplexTransportConnection d) {
connectionsLatch.countDown();

View File

@@ -34,7 +34,6 @@ import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.util.Random;
@@ -93,14 +92,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
getTransportProperties(3);
private final boolean alice = new Random().nextBoolean();
private RendezvousPollerImpl rendezvousPoller;
@Before
public void setUp() {
rendezvousPoller = new RendezvousPollerImpl(ioExecutor, scheduler, db,
identityManager, transportCrypto, rendezvousCrypto,
pluginManager, connectionManager, eventBus, clock);
}
private final RendezvousPollerImpl rendezvousPoller =
new RendezvousPollerImpl(ioExecutor, scheduler, db,
identityManager, transportCrypto, rendezvousCrypto,
pluginManager, connectionManager, eventBus, clock);
@Test
public void testAddsPendingContactsAndSchedulesPollingAtStartup()

View File

@@ -6,10 +6,10 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.DeferredSendHandler;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
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.Versions;
import org.briarproject.bramble.api.transport.StreamWriter;
@@ -30,6 +30,7 @@ import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals;
public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
@@ -40,8 +41,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
private final SyncRecordWriter recordWriter =
context.mock(SyncRecordWriter.class);
private final DeferredSendHandler deferredSendHandler =
context.mock(DeferredSendHandler.class);
private final ContactId contactId = getContactId();
private final TransportId transportId = getTransportId();
@@ -53,9 +52,10 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
@Test
public void testNothingToSend() throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter, deferredSendHandler,
streamWriter, recordWriter, sessionRecord,
MAX_FILE_PAYLOAD_BYTES);
Transaction noAckIdTxn = new Transaction(null, true);
@@ -92,13 +92,17 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}});
session.run();
assertEquals(emptyList(), sessionRecord.getAckedIds());
assertEquals(emptyList(), sessionRecord.getSentIds());
}
@Test
public void testSomethingToSend() throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter, deferredSendHandler,
streamWriter, recordWriter, sessionRecord,
MAX_FILE_PAYLOAD_BYTES);
Transaction ackIdTxn = new Transaction(null, true);
@@ -127,8 +131,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes));
oneOf(recordWriter).writeAck(with(any(Ack.class)));
oneOf(deferredSendHandler)
.onAckSent(singletonList(message.getId()));
// No more messages to ack
oneOf(db).transactionWithResult(with(true),
withDbCallable(noAckIdTxn));
@@ -150,7 +152,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
MAX_LATENCY, false);
will(returnValue(message1));
oneOf(recordWriter).writeMessage(message1);
oneOf(deferredSendHandler).onMessageSent(message1.getId());
// Send the end of stream marker
oneOf(streamWriter).sendEndOfStream();
// Remove listener
@@ -158,6 +159,11 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}});
session.run();
assertEquals(singletonList(message.getId()),
sessionRecord.getAckedIds());
assertEquals(singletonList(message1.getId()),
sessionRecord.getSentIds());
}
@Test
@@ -167,9 +173,10 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
long capacity = RECORD_HEADER_BYTES + MessageId.LENGTH * MAX_MESSAGE_IDS
+ RECORD_HEADER_BYTES + MessageId.LENGTH + MessageId.LENGTH - 1;
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter, deferredSendHandler, capacity);
streamWriter, recordWriter, sessionRecord, capacity);
Transaction ackIdTxn1 = new Transaction(null, true);
Transaction ackIdTxn2 = new Transaction(null, true);
@@ -184,6 +191,9 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}
List<MessageId> idsInSecondAck =
singletonList(new MessageId(getRandomId()));
List<MessageId> allIds = new ArrayList<>(MAX_MESSAGE_IDS + 1);
allIds.addAll(idsInFirstAck);
allIds.addAll(idsInSecondAck);
context.checking(new DbExpectations() {{
// Add listener
@@ -200,7 +210,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
will(returnValue(idsInFirstAck));
// Send the first ack record
oneOf(recordWriter).writeAck(with(any(Ack.class)));
oneOf(deferredSendHandler).onAckSent(idsInFirstAck);
// Calculate remaining capacity for acks
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes + firstAckRecordBytes));
@@ -211,7 +220,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
will(returnValue(idsInSecondAck));
// Send the second ack record
oneOf(recordWriter).writeAck(with(any(Ack.class)));
oneOf(deferredSendHandler).onAckSent(idsInSecondAck);
// Not enough capacity left for another ack
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes + firstAckRecordBytes
@@ -227,5 +235,8 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}});
session.run();
assertEquals(allIds, sessionRecord.getAckedIds());
assertEquals(emptyList(), sessionRecord.getSentIds());
}
}

View File

@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -44,12 +43,8 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
context.mock(MessageFactory.class);
private final RecordReader recordReader = context.mock(RecordReader.class);
private SyncRecordReader reader;
@Before
public void setUp() {
reader = new SyncRecordReaderImpl(messageFactory, recordReader);
}
private final SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
@Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {

View File

@@ -20,7 +20,6 @@ import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.junit.Before;
import org.junit.Test;
import java.util.LinkedHashMap;
@@ -70,11 +69,10 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
private final MessageContext validResultWithDependencies =
new MessageContext(metadata, singletonList(messageId1));
private ValidationManagerImpl vm;
private final ValidationManagerImpl vm =
new ValidationManagerImpl(db, dbExecutor, validationExecutor);
@Before
public void setUp() {
vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor);
public ValidationManagerImplTest() {
vm.registerMessageValidator(clientId, majorVersion, validator);
vm.registerIncomingMessageHook(clientId, majorVersion, hook);
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.Consumer;
import org.hamcrest.Description;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
public class ConsumeArgumentAction<T> implements Action {
private final Class<T> capturedClass;
private final int index;
private final Consumer<T> consumer;
public ConsumeArgumentAction(Class<T> capturedClass, int index,
Consumer<T> consumer) {
this.capturedClass = capturedClass;
this.index = index;
this.consumer = consumer;
}
@Override
public Object invoke(Invocation invocation) {
consumer.accept(capturedClass.cast(invocation.getParameter(index)));
return null;
}
@Override
public void describeTo(Description description) {
description.appendText("passes an argument to a consumer");
}
}

View File

@@ -22,7 +22,6 @@ import org.hamcrest.Description;
import org.jmock.Expectations;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
@@ -72,13 +71,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
private final SecretKey rootKey = getSecretKey();
private final Random random = new Random();
private TransportKeyManager transportKeyManager;
@Before
public void setUp() {
transportKeyManager = new TransportKeyManagerImpl(db, transportCrypto,
dbExecutor, scheduler, clock, transportId, maxLatency);
}
private final TransportKeyManager transportKeyManager =
new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
scheduler, clock, transportId, maxLatency);
@Test
public void testKeysAreUpdatedAtStartup() throws Exception {

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
@@ -11,7 +12,7 @@ import static org.junit.Assert.fail;
public class ByteUtilsTest extends BrambleTestCase {
@Test
public void testReadUint16() {
public void testReadUint16() throws FormatException {
byte[] b = StringUtils.fromHexString("00000000");
assertEquals(0, ByteUtils.readUint16(b, 1));
b = StringUtils.fromHexString("00000100");
@@ -33,7 +34,7 @@ public class ByteUtilsTest extends BrambleTestCase {
}
@Test
public void testReadUint32() {
public void testReadUint32() throws FormatException {
byte[] b = StringUtils.fromHexString("000000000000");
assertEquals(0, ByteUtils.readUint32(b, 1));
b = StringUtils.fromHexString("000000000100");
@@ -55,7 +56,7 @@ public class ByteUtilsTest extends BrambleTestCase {
}
@Test
public void testReadUint64() {
public void testReadUint64() throws FormatException {
byte[] b = StringUtils.fromHexString("00000000000000000000");
assertEquals(0L, ByteUtils.readUint64(b, 1));
b = StringUtils.fromHexString("00000000000000000100");

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