mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
6 Commits
beta-1.4.1
...
poll-own-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66d907d1b2 | ||
|
|
7d336c98e4 | ||
|
|
963e510c3b | ||
|
|
213d2f1da4 | ||
|
|
3038b92dbc | ||
|
|
7cd3c2890b |
@@ -98,7 +98,7 @@ bridge test:
|
||||
allow_failure: true
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
timeout: 4h
|
||||
timeout: 3h
|
||||
|
||||
mailbox integration test:
|
||||
extends: .optional_tests
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
Folder-Description:
|
||||
===================
|
||||
* `briar-*`: Specifically for the Briar app (Phone/Desktop/Headless)
|
||||
* `bramble-*`: The protocol stack - not necessarily Briar-dependent
|
||||
|
||||
---
|
||||
|
||||
* `*-api`: public stuff that can be referenced from other packages and modules - mostly interfaces + a few utility classes
|
||||
* `*-core`: implementations of api that are portable across Android/Desktop/Headless
|
||||
* `*-java`: implementations of api that are specific to Desktop & Headless
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10412
|
||||
versionName "1.4.12"
|
||||
versionCode 10408
|
||||
versionName "1.4.8"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -42,10 +42,8 @@ configurations {
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation 'androidx.annotation:annotation:1.5.0'
|
||||
tor "org.briarproject:tor-android:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
|
||||
tor "org.briarproject:snowflake-android:$snowflake_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
|
||||
@@ -18,7 +18,3 @@
|
||||
-dontnote com.google.common.**
|
||||
|
||||
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
|
||||
|
||||
# Keep all Jackson-serialisable classes and their members
|
||||
-keep interface com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
-keep @com.fasterxml.jackson.databind.annotation.JsonSerialize class * { *; }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.battery.AndroidBatteryModule;
|
||||
import org.briarproject.bramble.io.DnsModule;
|
||||
import org.briarproject.bramble.network.AndroidNetworkModule;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionModule;
|
||||
import org.briarproject.bramble.reporting.ReportingModule;
|
||||
@@ -19,7 +18,6 @@ import dagger.Module;
|
||||
AndroidTaskSchedulerModule.class,
|
||||
AndroidWakefulIoExecutorModule.class,
|
||||
CircumventionModule.class,
|
||||
DnsModule.class,
|
||||
ReportingModule.class,
|
||||
SocksModule.class
|
||||
})
|
||||
|
||||
@@ -86,8 +86,8 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
BluetoothConnectionFactory<BluetoothSocket> connectionFactory =
|
||||
new AndroidBluetoothConnectionFactory(connectionLimiter,
|
||||
wakeLockManager, timeoutMonitor);
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
|
||||
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
||||
connectionLimiter, connectionFactory, ioExecutor,
|
||||
wakefulIoExecutor, secureRandom, androidExecutor, app,
|
||||
|
||||
@@ -61,8 +61,8 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
|
||||
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
|
||||
wakefulIoExecutor, app, backoff, callback,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, CONNECTION_TIMEOUT);
|
||||
|
||||
@@ -9,7 +9,6 @@ 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;
|
||||
@@ -31,8 +30,6 @@ import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import androidx.annotation.ChecksSdkIntAtLeast;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.logging.Level.INFO;
|
||||
@@ -47,14 +44,13 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
|
||||
private static final String TOR_LIB_NAME = "libtor.so";
|
||||
private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
|
||||
private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so";
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AndroidTorPlugin.class.getName());
|
||||
|
||||
private final Application app;
|
||||
private final AndroidWakeLock wakeLock;
|
||||
private final File torLib, obfs4Lib, snowflakeLib;
|
||||
private final File torLib, obfs4Lib;
|
||||
|
||||
AndroidTorPlugin(Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
@@ -67,7 +63,6 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
AndroidWakeLockManager wakeLockManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
@@ -78,7 +73,7 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, backoff,
|
||||
circumventionProvider, batteryManager,
|
||||
torRendezvousCrypto, callback, architecture, maxLatency,
|
||||
maxIdleTime, torDirectory, torSocksPort, torControlPort);
|
||||
this.app = app;
|
||||
@@ -86,7 +81,6 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
|
||||
torLib = new File(nativeLibDir, TOR_LIB_NAME);
|
||||
obfs4Lib = new File(nativeLibDir, OBFS4_LIB_NAME);
|
||||
snowflakeLib = new File(nativeLibDir, SNOWFLAKE_LIB_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,12 +106,6 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
if (!enable) wakeLock.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ChecksSdkIntAtLeast(api = 25)
|
||||
protected boolean canVerifyLetsEncryptCerts() {
|
||||
return SDK_INT >= 25;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
@@ -134,43 +122,39 @@ class AndroidTorPlugin extends TorPlugin {
|
||||
return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getSnowflakeExecutableFile() {
|
||||
return snowflakeLib.exists()
|
||||
? snowflakeLib : super.getSnowflakeExecutableFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installTorExecutable() throws IOException {
|
||||
installExecutable(super.getTorExecutableFile(), torLib, TOR_LIB_NAME);
|
||||
File extracted = super.getTorExecutableFile();
|
||||
if (torLib.exists()) {
|
||||
// If an older version left behind a Tor binary, delete it
|
||||
if (extracted.exists()) {
|
||||
if (extracted.delete()) LOG.info("Deleted Tor binary");
|
||||
else LOG.info("Failed to delete Tor binary");
|
||||
}
|
||||
} else if (SDK_INT < 29) {
|
||||
// The binary wasn't extracted at install time. Try to extract it
|
||||
extractLibraryFromApk(TOR_LIB_NAME, extracted);
|
||||
} else {
|
||||
// No point extracting the binary, we won't be allowed to execute it
|
||||
throw new FileNotFoundException(torLib.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installObfs4Executable() throws IOException {
|
||||
installExecutable(super.getObfs4ExecutableFile(), obfs4Lib,
|
||||
OBFS4_LIB_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installSnowflakeExecutable() throws IOException {
|
||||
installExecutable(super.getSnowflakeExecutableFile(), snowflakeLib,
|
||||
SNOWFLAKE_LIB_NAME);
|
||||
}
|
||||
|
||||
private void installExecutable(File extracted, File lib, String libName)
|
||||
throws IOException {
|
||||
if (lib.exists()) {
|
||||
// If an older version left behind a binary, delete it
|
||||
File extracted = super.getObfs4ExecutableFile();
|
||||
if (obfs4Lib.exists()) {
|
||||
// If an older version left behind an obfs4 binary, delete it
|
||||
if (extracted.exists()) {
|
||||
if (extracted.delete()) LOG.info("Deleted old binary");
|
||||
else LOG.info("Failed to delete old binary");
|
||||
if (extracted.delete()) LOG.info("Deleted obfs4 binary");
|
||||
else LOG.info("Failed to delete obfs4 binary");
|
||||
}
|
||||
} else if (SDK_INT < 29) {
|
||||
// The binary wasn't extracted at install time. Try to extract it
|
||||
extractLibraryFromApk(libName, extracted);
|
||||
extractLibraryFromApk(OBFS4_LIB_NAME, extracted);
|
||||
} else {
|
||||
// No point extracting the binary, we won't be allowed to execute it
|
||||
throw new FileNotFoundException(lib.getAbsolutePath());
|
||||
throw new FileNotFoundException(obfs4Lib.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ 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;
|
||||
@@ -44,7 +42,6 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
@@ -56,7 +53,7 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
Application app,
|
||||
AndroidWakeLockManager wakeLockManager) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
eventBus, torSocketFactory, resourceProvider,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
this.app = app;
|
||||
@@ -76,14 +73,14 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
TorPlugin createPluginInstance(TorRendezvousCrypto torRendezvousCrypto,
|
||||
PluginCallback callback,
|
||||
String architecture) {
|
||||
return new AndroidTorPlugin(ioExecutor,
|
||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
||||
torSocketFactory, clock, resourceProvider,
|
||||
circumventionProvider, batteryManager, wakeLockManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'androidx.annotation:annotation:1.5.0:annotation-1.5.0.jar:261fb7c0210858500bab66d34354972a75166ab4182add283780b05513d6ec4a',
|
||||
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
|
||||
'com.android.tools.analytics-library:protos:30.0.3:protos-30.0.3.jar:f62b89dcd9de719c6a7b7e15fb1dd20e45b57222e675cf633607bd0ed6bca7e7',
|
||||
'com.android.tools.analytics-library:shared:30.0.3:shared-30.0.3.jar:05aa9ba3cc890354108521fdf99802565aae5dd6ca44a6ac8bb8d594d1c1cd15',
|
||||
@@ -88,9 +87,8 @@ dependencyVerification {
|
||||
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
|
||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||
'org.briarproject:obfs4proxy-android:0.0.14:obfs4proxy-android-0.0.14.jar:ad9b1ee4757b05867a19e993147bbb018bddd1f26ce3da746d5f037d5991a8c8',
|
||||
'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293',
|
||||
'org.briarproject:tor-android:0.4.5.14:tor-android-0.4.5.14.jar:7cf1beaa6c1db51fc8fac263aba9624ef289c3db29772509efcbc59f7057330a',
|
||||
'org.briarproject:obfs4proxy-android:0.0.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89',
|
||||
'org.briarproject:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
|
||||
@@ -131,12 +129,10 @@ dependencyVerification {
|
||||
'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901',
|
||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
|
||||
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
||||
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
|
||||
|
||||
@@ -7,7 +7,6 @@ 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 {
|
||||
@@ -46,14 +45,6 @@ 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.
|
||||
*/
|
||||
|
||||
@@ -126,11 +126,16 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
TransportKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any acks to send to the given contact.
|
||||
* Returns true if there are any acks or messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException;
|
||||
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
@@ -156,18 +161,6 @@ 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/>
|
||||
@@ -283,13 +276,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
Group getGroup(Transaction txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the ID of the group containing the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
GroupId getGroupId(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for the given group.
|
||||
* <p/>
|
||||
@@ -356,13 +342,13 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
Metadata query) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all messages received from the given contact that
|
||||
* need to be acknowledged.
|
||||
* Returns the IDs of some messages received from the given contact that
|
||||
* need to be acknowledged, up to the given number of messages.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
|
||||
int maxMessages) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of some messages that are eligible to be sent to the
|
||||
@@ -499,8 +485,6 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
* Returns the message with the given ID for transmission to the given
|
||||
* contact over a transport with the given maximum latency. Returns null
|
||||
* if the message is no longer visible to the contact.
|
||||
* <p/>
|
||||
* Read-only if {@code markAsSent} is false.
|
||||
*
|
||||
* @param markAsSent True if the message should be marked as sent.
|
||||
* If false it can be marked as sent by calling
|
||||
@@ -550,18 +534,15 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact over a transport with
|
||||
* 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.
|
||||
* 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.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(Transaction txn, ContactId c, long maxLatency)
|
||||
throws DbException;
|
||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the pending contact with the given ID.
|
||||
|
||||
@@ -37,14 +37,8 @@ public interface LifecycleManager {
|
||||
*/
|
||||
enum LifecycleState {
|
||||
|
||||
CREATED,
|
||||
STARTING,
|
||||
MIGRATING_DATABASE,
|
||||
COMPACTING_DATABASE,
|
||||
STARTING_SERVICES,
|
||||
RUNNING,
|
||||
STOPPING,
|
||||
STOPPED;
|
||||
STARTING, MIGRATING_DATABASE, COMPACTING_DATABASE, STARTING_SERVICES,
|
||||
RUNNING, STOPPING;
|
||||
|
||||
public boolean isAfter(LifecycleState state) {
|
||||
return ordinal() > state.ordinal();
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -66,8 +65,4 @@ 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);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_CLIENT_TOO_OLD;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.API_SERVER_TOO_OLD;
|
||||
|
||||
@NotNullByDefault
|
||||
public class MailboxHelper {
|
||||
class MailboxHelper {
|
||||
|
||||
/**
|
||||
* Returns the highest major version that both client and server support
|
||||
* or {@link MailboxConstants#API_SERVER_TOO_OLD} if the server is too old
|
||||
* or {@link MailboxConstants#API_CLIENT_TOO_OLD} if the client is too old.
|
||||
*/
|
||||
public static int getHighestCommonMajorVersion(
|
||||
static int getHighestCommonMajorVersion(
|
||||
List<MailboxVersion> client, List<MailboxVersion> server) {
|
||||
TreeSet<Integer> clientVersions = new TreeSet<>();
|
||||
for (MailboxVersion version : client) {
|
||||
@@ -35,13 +32,4 @@ public class MailboxHelper {
|
||||
return API_SERVER_TOO_OLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a client and server with the given API versions can
|
||||
* communicate with each other (ie, have any major API versions in common).
|
||||
*/
|
||||
public static boolean isClientCompatibleWithServer(
|
||||
List<MailboxVersion> client, List<MailboxVersion> server) {
|
||||
int common = getHighestCommonMajorVersion(client, server);
|
||||
return common != API_CLIENT_TOO_OLD && common != API_SERVER_TOO_OLD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ 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;
|
||||
|
||||
@@ -33,7 +32,7 @@ public abstract class MailboxId extends UniqueId {
|
||||
}
|
||||
try {
|
||||
return fromHexString(token);
|
||||
} catch (FormatException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidMailboxIdException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NullSafety;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -12,7 +11,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class MailboxProperties {
|
||||
|
||||
private final String onion;
|
||||
private final String baseUrl;
|
||||
private final MailboxAuthToken authToken;
|
||||
private final boolean owner;
|
||||
private final List<MailboxVersion> serverSupports;
|
||||
@@ -24,9 +23,9 @@ public class MailboxProperties {
|
||||
/**
|
||||
* Constructor for properties used by the mailbox's owner.
|
||||
*/
|
||||
public MailboxProperties(String onion, MailboxAuthToken authToken,
|
||||
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||
List<MailboxVersion> serverSupports) {
|
||||
this.onion = onion;
|
||||
this.baseUrl = baseUrl;
|
||||
this.authToken = authToken;
|
||||
this.owner = true;
|
||||
this.serverSupports = serverSupports;
|
||||
@@ -37,10 +36,10 @@ public class MailboxProperties {
|
||||
/**
|
||||
* Constructor for properties used by a contact of the mailbox's owner.
|
||||
*/
|
||||
public MailboxProperties(String onion, MailboxAuthToken authToken,
|
||||
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
|
||||
MailboxFolderId outboxId) {
|
||||
this.onion = onion;
|
||||
this.baseUrl = baseUrl;
|
||||
this.authToken = authToken;
|
||||
this.owner = false;
|
||||
this.serverSupports = serverSupports;
|
||||
@@ -48,11 +47,13 @@ public class MailboxProperties {
|
||||
this.outboxId = outboxId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the onion address of the mailbox, excluding the .onion suffix.
|
||||
*/
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public String getOnion() {
|
||||
return onion;
|
||||
return baseUrl.replaceFirst("^http://", "")
|
||||
.replaceFirst("\\.onion$", "");
|
||||
}
|
||||
|
||||
public MailboxAuthToken getAuthToken() {
|
||||
@@ -76,23 +77,4 @@ public class MailboxProperties {
|
||||
public MailboxFolderId getOutboxId() {
|
||||
return outboxId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof MailboxProperties) {
|
||||
MailboxProperties m = (MailboxProperties) o;
|
||||
return owner == m.owner &&
|
||||
onion.equals(m.onion) &&
|
||||
authToken.equals(m.authToken) &&
|
||||
NullSafety.equals(inboxId, m.inboxId) &&
|
||||
NullSafety.equals(outboxId, m.outboxId) &&
|
||||
serverSupports.equals(m.serverSupports);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return authToken.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ public interface MailboxSettingsManager {
|
||||
|
||||
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
||||
|
||||
void recordSuccessfulConnection(Transaction txn, long now,
|
||||
List<MailboxVersion> versions) throws DbException;
|
||||
void recordSuccessfulConnection(Transaction txn, long now)
|
||||
throws DbException;
|
||||
|
||||
void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||
throws DbException;
|
||||
@@ -46,28 +46,20 @@ public interface MailboxSettingsManager {
|
||||
|
||||
interface MailboxHook {
|
||||
/**
|
||||
* Called when Briar is paired with a mailbox.
|
||||
* Called when Briar is paired with a mailbox
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
* @param ownOnion Our new mailbox's onion (56 base32 chars)
|
||||
*/
|
||||
void mailboxPaired(Transaction txn, MailboxProperties p)
|
||||
void mailboxPaired(Transaction txn, String ownOnion,
|
||||
List<MailboxVersion> serverSupports)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Called when the mailbox is unpaired.
|
||||
* Called when the mailbox is unpaired
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
*/
|
||||
void mailboxUnpaired(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Called when we receive our mailbox's server-supported API versions.
|
||||
* This happens whenever we successfully check the connectivity of
|
||||
* our mailbox, so this hook may be called frequently.
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
*/
|
||||
void serverSupportedVersionsReceived(Transaction txn,
|
||||
List<MailboxVersion> serverSupports) throws DbException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,13 +67,6 @@ public class MailboxStatus {
|
||||
return attemptsSinceSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mailbox's supported API versions.
|
||||
*/
|
||||
public List<MailboxVersion> getServerSupports() {
|
||||
return serverSupports;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this status indicates a problem with the mailbox.
|
||||
*/
|
||||
|
||||
@@ -29,35 +29,19 @@ 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 onion address of the mailbox, excluding the .onion suffix.
|
||||
* The required properties of an update message with a mailbox.
|
||||
*/
|
||||
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 {@link #PROP_KEY_ONION} property.
|
||||
* Length of the Onion property.
|
||||
*/
|
||||
int PROP_ONION_LENGTH = 56;
|
||||
|
||||
@@ -79,32 +63,9 @@ public interface MailboxUpdateManager {
|
||||
*/
|
||||
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
|
||||
|
||||
/**
|
||||
* Key in the client's local group for storing the serverSupports list that
|
||||
* was last sent out, if any.
|
||||
*/
|
||||
String GROUP_KEY_SENT_SERVER_SUPPORTS = "sentServerSupports";
|
||||
|
||||
/**
|
||||
* Returns the latest {@link MailboxUpdate} sent to the given contact.
|
||||
* <p>
|
||||
* If we have our own mailbox then the update will be a
|
||||
* {@link MailboxUpdateWithMailbox} containing the
|
||||
* {@link MailboxProperties} the contact should use for communicating with
|
||||
* our mailbox.
|
||||
*/
|
||||
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
|
||||
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;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a mailbox is paired.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxPairedEvent extends Event {
|
||||
|
||||
private final MailboxProperties properties;
|
||||
private final Map<ContactId, MailboxUpdateWithMailbox> localUpdates;
|
||||
|
||||
public MailboxPairedEvent(MailboxProperties properties,
|
||||
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
|
||||
this.properties = properties;
|
||||
this.localUpdates = localUpdates;
|
||||
}
|
||||
|
||||
public MailboxProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public Map<ContactId, MailboxUpdateWithMailbox> getLocalUpdates() {
|
||||
return localUpdates;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a mailbox is unpaired.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxUnpairedEvent extends Event {
|
||||
|
||||
private final Map<ContactId, MailboxUpdate> localUpdates;
|
||||
|
||||
public MailboxUnpairedEvent(Map<ContactId, MailboxUpdate> localUpdates) {
|
||||
this.localUpdates = localUpdates;
|
||||
}
|
||||
|
||||
public Map<ContactId, MailboxUpdate> getLocalUpdates() {
|
||||
return localUpdates;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.briarproject.bramble.api.mailbox.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the first mailbox update is sent to a
|
||||
* newly added contact, which happens in the same transaction in which the
|
||||
* contact is added.
|
||||
* <p>
|
||||
* This event is not broadcast when the first mailbox update is sent to an
|
||||
* existing contact when setting up the
|
||||
* {@link MailboxUpdateManager mailbox update client}.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxUpdateSentToNewContactEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final MailboxUpdate mailboxUpdate;
|
||||
|
||||
public MailboxUpdateSentToNewContactEvent(ContactId contactId,
|
||||
MailboxUpdate mailboxUpdate) {
|
||||
this.contactId = contactId;
|
||||
this.mailboxUpdate = mailboxUpdate;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public MailboxUpdate getMailboxUpdate() {
|
||||
return mailboxUpdate;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
|
||||
public interface BackoffFactory {
|
||||
|
||||
Backoff createBackoff(int minInterval, int maxInterval,
|
||||
double base);
|
||||
Backoff createBackoff(EventBus eventBus, TransportId transportId,
|
||||
int minInterval, int maxInterval, double base);
|
||||
}
|
||||
|
||||
@@ -56,4 +56,9 @@ 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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a plugin's polling interval decreases.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class PollingIntervalDecreasedEvent extends Event {
|
||||
|
||||
private final TransportId transportId;
|
||||
|
||||
public PollingIntervalDecreasedEvent(TransportId transportId) {
|
||||
this.transportId = transportId;
|
||||
}
|
||||
|
||||
public TransportId getTransportId() {
|
||||
return transportId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An interface for holding the IDs of messages sent and acked during an
|
||||
* outgoing {@link SyncSession} so they can be recorded in the DB as sent
|
||||
* or acked at some later time.
|
||||
*/
|
||||
public interface DeferredSendHandler {
|
||||
|
||||
void onAckSent(Collection<MessageId> acked);
|
||||
|
||||
void onMessageSent(MessageId sent);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* A container for holding the IDs of messages sent and acked during an
|
||||
* outgoing {@link SyncSession}, so they can be recorded in the DB as sent
|
||||
* or acked at some later time.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class OutgoingSessionRecord {
|
||||
|
||||
private final Collection<MessageId> ackedIds = new CopyOnWriteArrayList<>();
|
||||
private final Collection<MessageId> sentIds = new CopyOnWriteArrayList<>();
|
||||
|
||||
public void onAckSent(Collection<MessageId> acked) {
|
||||
ackedIds.addAll(acked);
|
||||
}
|
||||
|
||||
public void onMessageSent(MessageId sent) {
|
||||
sentIds.add(sent);
|
||||
}
|
||||
|
||||
public Collection<MessageId> getAckedIds() {
|
||||
return ackedIds;
|
||||
}
|
||||
|
||||
public Collection<MessageId> getSentIds() {
|
||||
return sentIds;
|
||||
}
|
||||
}
|
||||
@@ -12,30 +12,12 @@ 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);
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
|
||||
@@ -16,19 +15,12 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class GroupVisibilityUpdatedEvent extends Event {
|
||||
|
||||
private final Visibility visibility;
|
||||
private final Collection<ContactId> affected;
|
||||
|
||||
public GroupVisibilityUpdatedEvent(Visibility visibility,
|
||||
Collection<ContactId> affected) {
|
||||
this.visibility = visibility;
|
||||
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
|
||||
this.affected = affected;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contacts affected by the update.
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
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 org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
@@ -19,32 +14,12 @@ import javax.annotation.concurrent.Immutable;
|
||||
public class MessageSharedEvent extends Event {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final GroupId groupId;
|
||||
private final Map<ContactId, Boolean> groupVisibility;
|
||||
|
||||
public MessageSharedEvent(MessageId message, GroupId groupId,
|
||||
Map<ContactId, Boolean> groupVisibility) {
|
||||
public MessageSharedEvent(MessageId message) {
|
||||
this.messageId = message;
|
||||
this.groupId = groupId;
|
||||
this.groupVisibility = groupVisibility;
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of all contacts for which the visibility of the
|
||||
* message's group is either {@link Visibility#SHARED shared} or
|
||||
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
|
||||
* group is {@link Visibility#SHARED shared} or false if the group is
|
||||
* {@link Visibility#VISIBLE visible}.
|
||||
*/
|
||||
public Map<ContactId, Boolean> getGroupVisibility() {
|
||||
return groupVisibility;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class IoUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(File f) {
|
||||
private static void delete(File f) {
|
||||
if (!f.delete() && LOG.isLoggable(WARNING))
|
||||
LOG.warning("Could not delete " + f.getAbsolutePath());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.util;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -96,10 +95,10 @@ public class StringUtils {
|
||||
/**
|
||||
* Converts the given hex string to a byte array.
|
||||
*/
|
||||
public static byte[] fromHexString(String hex) throws FormatException {
|
||||
public static byte[] fromHexString(String hex) {
|
||||
int len = hex.length();
|
||||
if (len % 2 != 0)
|
||||
throw new FormatException();
|
||||
throw new IllegalArgumentException("Not a hex string");
|
||||
byte[] bytes = new byte[len / 2];
|
||||
for (int i = 0, j = 0; i < len; i += 2, j++) {
|
||||
int high = hexDigitToInt(hex.charAt(i));
|
||||
@@ -109,11 +108,11 @@ public class StringUtils {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static int hexDigitToInt(char c) throws FormatException {
|
||||
private static int hexDigitToInt(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
throw new FormatException();
|
||||
throw new IllegalArgumentException("Not a hex digit: " + c);
|
||||
}
|
||||
|
||||
public static String trim(String s) {
|
||||
@@ -131,13 +130,13 @@ public class StringUtils {
|
||||
return MAC.matcher(mac).matches();
|
||||
}
|
||||
|
||||
public static byte[] macToBytes(String mac) throws FormatException {
|
||||
if (!MAC.matcher(mac).matches()) throw new FormatException();
|
||||
public static byte[] macToBytes(String mac) {
|
||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||
return fromHexString(mac.replaceAll(":", ""));
|
||||
}
|
||||
|
||||
public static String macToString(byte[] mac) throws FormatException {
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
public static String macToString(byte[] mac) {
|
||||
if (mac.length != 6) throw new IllegalArgumentException();
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (byte b : mac) {
|
||||
if (s.length() > 0) s.append(':');
|
||||
|
||||
@@ -230,14 +230,14 @@ public class TestUtils {
|
||||
|
||||
public static MailboxProperties getMailboxProperties(boolean owner,
|
||||
List<MailboxVersion> serverSupports) {
|
||||
String onion = getRandomString(56);
|
||||
String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
|
||||
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
|
||||
if (owner) {
|
||||
return new MailboxProperties(onion, authToken, serverSupports);
|
||||
return new MailboxProperties(baseUrl, authToken, serverSupports);
|
||||
}
|
||||
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
|
||||
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
|
||||
return new MailboxProperties(onion, authToken, serverSupports,
|
||||
return new MailboxProperties(baseUrl, authToken, serverSupports,
|
||||
inboxId, outboxId);
|
||||
}
|
||||
|
||||
@@ -338,17 +338,6 @@ public class TestUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <E extends Event> E getEvent(Transaction txn,
|
||||
Class<E> eventClass) {
|
||||
for (CommitAction action : txn.getActions()) {
|
||||
if (action instanceof EventAction) {
|
||||
Event event = ((EventAction) action).getEvent();
|
||||
if (eventClass.isInstance(event)) return eventClass.cast(event);
|
||||
}
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static boolean isCryptoStrengthUnlimited() {
|
||||
try {
|
||||
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")
|
||||
|
||||
@@ -16,7 +16,7 @@ dependencies {
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||
implementation 'org.briarproject:jtorctl:0.5'
|
||||
implementation 'org.briarproject:jtorctl:0.4'
|
||||
|
||||
//noinspection GradleDependency
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -210,13 +209,7 @@ class AccountManagerImpl implements AccountManager {
|
||||
LOG.warning("Failed to load encrypted database key");
|
||||
throw new DecryptionException(INVALID_CIPHERTEXT);
|
||||
}
|
||||
byte[] ciphertext;
|
||||
try {
|
||||
ciphertext = fromHexString(hex);
|
||||
} catch (FormatException e) {
|
||||
LOG.warning("Encrypted database key has invalid format");
|
||||
throw new DecryptionException(INVALID_CIPHERTEXT);
|
||||
}
|
||||
byte[] ciphertext = fromHexString(hex);
|
||||
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
|
||||
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
||||
keyStrengthener);
|
||||
|
||||
@@ -52,7 +52,6 @@ import java.util.Map.Entry;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.sort;
|
||||
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
@@ -457,7 +456,8 @@ class ClientHelperImpl implements ClientHelper {
|
||||
checkLength(inboxId, UniqueId.LENGTH);
|
||||
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
||||
checkLength(outboxId, UniqueId.LENGTH);
|
||||
MailboxProperties props = new MailboxProperties(onion,
|
||||
String baseUrl = "http://" + onion + ".onion"; // TODO
|
||||
MailboxProperties props = new MailboxProperties(baseUrl,
|
||||
new MailboxAuthToken(authToken), serverSupportsList,
|
||||
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
|
||||
return new MailboxUpdateWithMailbox(clientSupportsList, props);
|
||||
@@ -475,8 +475,6 @@ class ClientHelperImpl implements ClientHelper {
|
||||
list.add(new MailboxVersion(element.getLong(0).intValue(),
|
||||
element.getLong(1).intValue()));
|
||||
}
|
||||
// Sort the list of versions for easier comparison
|
||||
sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ 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;
|
||||
@@ -101,16 +100,7 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
TransportConnectionWriter w) {
|
||||
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
|
||||
connectionRegistry, streamReaderFactory, streamWriterFactory,
|
||||
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));
|
||||
syncSessionFactory, transportPropertyManager, c, t, w));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -17,8 +16,6 @@ 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;
|
||||
@@ -29,8 +26,6 @@ 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,
|
||||
@@ -39,15 +34,13 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
SyncSessionFactory syncSessionFactory,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
ContactId contactId, TransportId transportId,
|
||||
TransportConnectionWriter writer,
|
||||
@Nullable OutgoingSessionRecord sessionRecord) {
|
||||
TransportConnectionWriter writer) {
|
||||
super(keyManager, connectionRegistry, streamReaderFactory,
|
||||
streamWriterFactory, syncSessionFactory,
|
||||
transportPropertyManager);
|
||||
this.contactId = contactId;
|
||||
this.transportId = transportId;
|
||||
this.writer = writer;
|
||||
this.sessionRecord = sessionRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,16 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||
w.getOutputStream(), ctx);
|
||||
ContactId c = requireNonNull(ctx.getContactId());
|
||||
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);
|
||||
}
|
||||
// Use eager retransmission if the transport is lossy and cheap
|
||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
||||
streamWriter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,11 +163,16 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if there are any acks to send to the given contact.
|
||||
* Returns true if there are any acks or messages to send to the given
|
||||
* contact over a transport with the given maximum latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*
|
||||
* @param eager True if messages that are not yet due for retransmission
|
||||
* should be included
|
||||
*/
|
||||
boolean containsAcksToSend(T txn, ContactId c) throws DbException;
|
||||
boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
|
||||
boolean eager) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
@@ -207,18 +212,6 @@ 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/>
|
||||
@@ -320,13 +313,6 @@ interface Database<T> {
|
||||
*/
|
||||
Group getGroup(T txn, GroupId g) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the ID of the group containing the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
GroupId getGroupId(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for the given group.
|
||||
* <p/>
|
||||
@@ -352,11 +338,8 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all contacts for which the given group's visibility
|
||||
* is either {@link Visibility#SHARED shared} or
|
||||
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
|
||||
* group is {@link Visibility#SHARED shared} or false if the group is
|
||||
* {@link Visibility#VISIBLE visible}.
|
||||
* Returns the IDs of all contacts to which the given group's visibility is
|
||||
* either {@link Visibility VISIBLE} or {@link Visibility SHARED}.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -597,16 +580,13 @@ 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 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.
|
||||
* 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.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(T txn, ContactId c, long maxLatency)
|
||||
throws DbException;
|
||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the pending contact with the given ID.
|
||||
|
||||
@@ -287,12 +287,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
transaction.attach(new MessageAddedEvent(m, null));
|
||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||
DELIVERED));
|
||||
if (shared) {
|
||||
Map<ContactId, Boolean> visibility =
|
||||
db.getGroupVisibility(txn, m.getGroupId());
|
||||
transaction.attach(new MessageSharedEvent(m.getId(),
|
||||
m.getGroupId(), visibility));
|
||||
}
|
||||
if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
|
||||
}
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
}
|
||||
@@ -347,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAcksToSend(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.containsAcksToSend(txn, c);
|
||||
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -378,15 +373,6 @@ 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 {
|
||||
@@ -555,15 +541,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getGroup(txn, g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupId getGroupId(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
return db.getGroupId(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata getGroupMetadata(Transaction transaction, GroupId g)
|
||||
throws DbException {
|
||||
@@ -634,11 +611,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
||||
ContactId c) throws DbException {
|
||||
ContactId c, int maxMessages) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.getMessagesToAck(txn, c, Integer.MAX_VALUE);
|
||||
return db.getMessagesToAck(txn, c, maxMessages);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -760,9 +737,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
public Message getMessageToSend(Transaction transaction, ContactId c,
|
||||
MessageId m, long maxLatency, boolean markAsSent)
|
||||
throws DbException {
|
||||
if (markAsSent && transaction.isReadOnly()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
@@ -830,10 +805,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c,
|
||||
long maxLatency) throws DbException {
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c, maxLatency);
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1041,8 +1016,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.getGroupVisibility(txn, id).keySet();
|
||||
db.removeGroup(txn, id);
|
||||
transaction.attach(new GroupRemovedEvent(g));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
|
||||
affected));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1113,7 +1087,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
db.lowerAckFlag(txn, c, acked);
|
||||
List<MessageId> visible = new ArrayList<>(acked.size());
|
||||
for (MessageId m : acked) {
|
||||
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
|
||||
}
|
||||
db.lowerAckFlag(txn, c, visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1163,7 +1141,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(v, affected));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1196,9 +1174,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (db.getMessageState(txn, m) != DELIVERED)
|
||||
throw new IllegalArgumentException("Shared undelivered message");
|
||||
db.setMessageShared(txn, m, true);
|
||||
GroupId g = db.getGroupId(txn, m);
|
||||
Map<ContactId, Boolean> visibility = db.getGroupVisibility(txn, g);
|
||||
transaction.attach(new MessageSharedEvent(m, g, visibility));
|
||||
transaction.attach(new MessageSharedEvent(m));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1147,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAcksToSend(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -1160,7 +1160,34 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
boolean acksToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return acksToSend;
|
||||
if (acksToSend) return true;
|
||||
if (eager) {
|
||||
sql = "SELECT NULL from statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return messagesToSend;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
@@ -1280,46 +1307,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsMessagesToSend(Connection txn, ContactId c,
|
||||
long maxLatency, boolean eager) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
if (eager) {
|
||||
String sql = "SELECT NULL from statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, maxLatency);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return messagesToSend;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPendingContact(Connection txn, PendingContactId p)
|
||||
throws DbException {
|
||||
@@ -1683,27 +1670,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupId getGroupId(Connection txn, MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT groupId FROM messages WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId g = new GroupId(rs.getBytes(1));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return g;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Group> getGroups(Connection txn, ClientId c,
|
||||
int majorVersion) throws DbException {
|
||||
@@ -2511,28 +2477,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
|
||||
public long getNextSendTime(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// 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"
|
||||
String sql = "SELECT expiry FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
|
||||
@@ -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.atomic.AtomicReference;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
@@ -29,12 +29,10 @@ import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.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;
|
||||
@@ -62,11 +60,12 @@ 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 final AtomicReference<LifecycleState> state =
|
||||
new AtomicReference<>(CREATED);
|
||||
|
||||
private volatile LifecycleState state = STARTING;
|
||||
|
||||
@Inject
|
||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||
@@ -103,8 +102,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
|
||||
@Override
|
||||
public StartResult startServices(SecretKey dbKey) {
|
||||
if (!state.compareAndSet(CREATED, STARTING)) {
|
||||
LOG.warning("Already running");
|
||||
if (!startStopSemaphore.tryAcquire()) {
|
||||
LOG.info("Already starting or stopping");
|
||||
return ALREADY_RUNNING;
|
||||
}
|
||||
long now = clock.currentTimeMillis();
|
||||
@@ -136,7 +135,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
});
|
||||
|
||||
LOG.info("Starting services");
|
||||
state.set(STARTING_SERVICES);
|
||||
state = STARTING_SERVICES;
|
||||
dbLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||
|
||||
@@ -149,7 +148,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
}
|
||||
}
|
||||
|
||||
state.set(RUNNING);
|
||||
state = RUNNING;
|
||||
startupLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||
return SUCCESS;
|
||||
@@ -165,58 +164,63 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
} catch (ServiceException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return SERVICE_ERROR;
|
||||
} finally {
|
||||
startStopSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseMigration() {
|
||||
state.set(MIGRATING_DATABASE);
|
||||
state = MIGRATING_DATABASE;
|
||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseCompaction() {
|
||||
state.set(COMPACTING_DATABASE);
|
||||
state = COMPACTING_DATABASE;
|
||||
eventBus.broadcast(new LifecycleEvent(COMPACTING_DATABASE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServices() {
|
||||
if (!state.compareAndSet(RUNNING, STOPPING)) {
|
||||
LOG.warning("Not running");
|
||||
try {
|
||||
startStopSemaphore.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting to stop services");
|
||||
return;
|
||||
}
|
||||
LOG.info("Stopping services");
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
for (Service s : services) {
|
||||
try {
|
||||
try {
|
||||
if (state == STOPPING) {
|
||||
LOG.info("Already stopped");
|
||||
return;
|
||||
}
|
||||
LOG.info("Stopping services");
|
||||
state = STOPPING;
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
for (Service s : services) {
|
||||
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());
|
||||
for (ExecutorService e : executors) {
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
LOG.fine("Stopping executor "
|
||||
+ e.getClass().getSimpleName());
|
||||
}
|
||||
e.shutdownNow();
|
||||
}
|
||||
e.shutdownNow();
|
||||
}
|
||||
try {
|
||||
long start = now();
|
||||
db.close();
|
||||
logDuration(LOG, "Closing database", start);
|
||||
} catch (DbException e) {
|
||||
shutdownLatch.countDown();
|
||||
} catch (DbException | ServiceException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
} finally {
|
||||
startStopSemaphore.release();
|
||||
}
|
||||
state.set(STOPPED);
|
||||
shutdownLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPED));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -236,6 +240,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state.get();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class ContactMailboxClient implements MailboxClient {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ContactMailboxClient.class.getName());
|
||||
|
||||
private final MailboxWorkerFactory workerFactory;
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final TorReachabilityMonitor reachabilityMonitor;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private MailboxWorker uploadWorker = null, downloadWorker = null;
|
||||
|
||||
ContactMailboxClient(MailboxWorkerFactory workerFactory,
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor) {
|
||||
this.workerFactory = workerFactory;
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.reachabilityMonitor = reachabilityMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
// Nothing to do until contact is assigned
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
MailboxWorker uploadWorker, downloadWorker;
|
||||
synchronized (lock) {
|
||||
uploadWorker = this.uploadWorker;
|
||||
this.uploadWorker = null;
|
||||
downloadWorker = this.downloadWorker;
|
||||
this.downloadWorker = null;
|
||||
}
|
||||
if (uploadWorker != null) uploadWorker.destroy();
|
||||
if (downloadWorker != null) downloadWorker.destroy();
|
||||
// The connectivity checker belongs to the client, so it should be
|
||||
// destroyed. The Tor reachability monitor is shared between clients,
|
||||
// so it should not be destroyed
|
||||
connectivityChecker.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignContactForUpload(ContactId contactId,
|
||||
MailboxProperties properties, MailboxFolderId folderId) {
|
||||
LOG.info("Contact assigned for upload");
|
||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||
// For a contact's mailbox we should always be uploading to the outbox
|
||||
// assigned to us by the contact
|
||||
if (!folderId.equals(properties.getOutboxId())) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
|
||||
connectivityChecker, properties, folderId, contactId);
|
||||
synchronized (lock) {
|
||||
if (this.uploadWorker != null) throw new IllegalStateException();
|
||||
this.uploadWorker = uploadWorker;
|
||||
}
|
||||
uploadWorker.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deassignContactForUpload(ContactId contactId) {
|
||||
LOG.info("Contact deassigned for upload");
|
||||
MailboxWorker uploadWorker;
|
||||
synchronized (lock) {
|
||||
uploadWorker = this.uploadWorker;
|
||||
this.uploadWorker = null;
|
||||
}
|
||||
if (uploadWorker != null) uploadWorker.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignContactForDownload(ContactId contactId,
|
||||
MailboxProperties properties, MailboxFolderId folderId) {
|
||||
LOG.info("Contact assigned for download");
|
||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||
// For a contact's mailbox we should always be downloading from the
|
||||
// inbox assigned to us by the contact
|
||||
if (!folderId.equals(properties.getInboxId())) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
MailboxWorker downloadWorker =
|
||||
workerFactory.createDownloadWorkerForContactMailbox(
|
||||
connectivityChecker, reachabilityMonitor, properties);
|
||||
synchronized (lock) {
|
||||
if (this.downloadWorker != null) throw new IllegalStateException();
|
||||
this.downloadWorker = downloadWorker;
|
||||
}
|
||||
downloadWorker.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deassignContactForDownload(ContactId contactId) {
|
||||
LOG.info("Contact deassigned for download");
|
||||
MailboxWorker downloadWorker;
|
||||
synchronized (lock) {
|
||||
downloadWorker = this.downloadWorker;
|
||||
this.downloadWorker = null;
|
||||
}
|
||||
if (downloadWorker != null) downloadWorker.destroy();
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,14 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ContactMailboxConnectivityChecker.class.getName());
|
||||
|
||||
private final MailboxApi mailboxApi;
|
||||
|
||||
@Inject
|
||||
ContactMailboxConnectivityChecker(Clock clock,
|
||||
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
|
||||
super(clock, mailboxApiCaller);
|
||||
@@ -32,9 +23,7 @@ class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||
return new SimpleApiCall(() -> {
|
||||
LOG.info("Checking connectivity of contact's mailbox");
|
||||
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
||||
LOG.info("Contact's mailbox is reachable");
|
||||
// Call the observers and cache the result
|
||||
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
||||
});
|
||||
|
||||
@@ -1,25 +1,73 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
|
||||
class ContactMailboxDownloadWorker implements MailboxWorker,
|
||||
ConnectivityObserver, TorReachabilityObserver {
|
||||
|
||||
/**
|
||||
* When the worker is started it waits for a connectivity check, then
|
||||
* starts its first download cycle: checking the inbox, downloading and
|
||||
* deleting any files, and checking again until the inbox is empty.
|
||||
* <p>
|
||||
* The worker then waits for our Tor hidden service to be reachable before
|
||||
* starting its second download cycle. This ensures that if a contact
|
||||
* tried and failed to connect to our hidden service before it was
|
||||
* reachable, and therefore uploaded a file to the mailbox instead, we'll
|
||||
* find the file in the second download cycle.
|
||||
*/
|
||||
private enum State {
|
||||
CREATED,
|
||||
CONNECTIVITY_CHECK,
|
||||
DOWNLOAD_CYCLE_1,
|
||||
WAITING_FOR_TOR,
|
||||
DOWNLOAD_CYCLE_2,
|
||||
FINISHED,
|
||||
DESTROYED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ContactMailboxDownloadWorker.class.getName());
|
||||
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final TorReachabilityMonitor torReachabilityMonitor;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxFileManager mailboxFileManager;
|
||||
private final MailboxProperties mailboxProperties;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private State state = State.CREATED;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable apiCall = null;
|
||||
|
||||
ContactMailboxDownloadWorker(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
@@ -28,14 +76,57 @@ class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager,
|
||||
MailboxProperties mailboxProperties) {
|
||||
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
|
||||
mailboxApi, mailboxFileManager, mailboxProperties);
|
||||
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.torReachabilityMonitor = torReachabilityMonitor;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxFileManager = mailboxFileManager;
|
||||
this.mailboxProperties = mailboxProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApiCall createApiCallForDownloadCycle() {
|
||||
return new SimpleApiCall(this::apiCallListInbox);
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
synchronized (lock) {
|
||||
// Don't allow the worker to be reused
|
||||
if (state != State.CREATED) throw new IllegalStateException();
|
||||
state = State.CONNECTIVITY_CHECK;
|
||||
}
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
Cancellable apiCall;
|
||||
synchronized (lock) {
|
||||
state = State.DESTROYED;
|
||||
apiCall = this.apiCall;
|
||||
this.apiCall = null;
|
||||
}
|
||||
if (apiCall != null) apiCall.cancel();
|
||||
connectivityChecker.removeObserver(this);
|
||||
torReachabilityMonitor.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
LOG.info("Connectivity check succeeded");
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTIVITY_CHECK) return;
|
||||
state = State.DOWNLOAD_CYCLE_1;
|
||||
// Start first download cycle
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallListInbox));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallListInbox() throws IOException, ApiException {
|
||||
@@ -43,23 +134,108 @@ class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
LOG.info("Listing inbox");
|
||||
MailboxFolderId folderId =
|
||||
requireNonNull(mailboxProperties.getInboxId());
|
||||
List<MailboxFile> files;
|
||||
try {
|
||||
files = mailboxApi.getFiles(mailboxProperties, folderId);
|
||||
} catch (TolerableFailureException e) {
|
||||
LOG.warning("Inbox folder does not exist");
|
||||
files = emptyList();
|
||||
}
|
||||
if (files.isEmpty()) {
|
||||
onDownloadCycleFinished();
|
||||
} else {
|
||||
Queue<FolderFile> queue = new LinkedList<>();
|
||||
for (MailboxFile file : files) {
|
||||
queue.add(new FolderFile(folderId, file.name));
|
||||
List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()));
|
||||
if (files.isEmpty()) onDownloadCycleFinished();
|
||||
else downloadNextFile(new LinkedList<>(files));
|
||||
}
|
||||
|
||||
private void onDownloadCycleFinished() {
|
||||
boolean addObserver = false;
|
||||
synchronized (lock) {
|
||||
if (state == State.DOWNLOAD_CYCLE_1) {
|
||||
LOG.info("First download cycle finished");
|
||||
state = State.WAITING_FOR_TOR;
|
||||
addObserver = true;
|
||||
} else if (state == State.DOWNLOAD_CYCLE_2) {
|
||||
LOG.info("Second download cycle finished");
|
||||
state = State.FINISHED;
|
||||
}
|
||||
}
|
||||
if (addObserver) {
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
torReachabilityMonitor.addOneShotObserver(this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) torReachabilityMonitor.removeObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadNextFile(Queue<MailboxFile> queue) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
MailboxFile file = queue.remove();
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() -> apiCallDownloadFile(file, queue)));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallDownloadFile(MailboxFile file,
|
||||
Queue<MailboxFile> queue) throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
LOG.info("Downloading file");
|
||||
File tempFile = mailboxFileManager.createTempFileForDownload();
|
||||
try {
|
||||
mailboxApi.getFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()),
|
||||
file.name, tempFile);
|
||||
} catch (IOException | ApiException e) {
|
||||
if (!tempFile.delete()) {
|
||||
LOG.warning("Failed to delete temporary file");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
mailboxFileManager.handleDownloadedFile(tempFile);
|
||||
deleteFile(file, queue);
|
||||
}
|
||||
|
||||
private void deleteFile(MailboxFile file, Queue<MailboxFile> queue) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallDeleteFile(MailboxFile file, Queue<MailboxFile> queue)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
try {
|
||||
mailboxApi.deleteFile(mailboxProperties,
|
||||
requireNonNull(mailboxProperties.getInboxId()), file.name);
|
||||
} catch (TolerableFailureException e) {
|
||||
// Catch this so we can continue to the next file
|
||||
logException(LOG, INFO, e);
|
||||
}
|
||||
if (queue.isEmpty()) {
|
||||
// List the inbox again to check for files that may have arrived
|
||||
// while we were downloading
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallListInbox));
|
||||
}
|
||||
} else {
|
||||
downloadNextFile(queue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTorReachable() {
|
||||
LOG.info("Our Tor hidden service is reachable");
|
||||
synchronized (lock) {
|
||||
if (state != State.WAITING_FOR_TOR) return;
|
||||
state = State.DOWNLOAD_CYCLE_2;
|
||||
// Start second download cycle
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallListInbox));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +91,9 @@ interface MailboxApi {
|
||||
* Used by owner and contacts to list their files to retrieve.
|
||||
* <p>
|
||||
* Returns 200 OK with the list of files in JSON.
|
||||
*
|
||||
* @throws TolerableFailureException if response code is 404 (folder does
|
||||
* not exist or client is not authorised to download from it)
|
||||
*/
|
||||
List<MailboxFile> getFiles(MailboxProperties properties,
|
||||
MailboxFolderId folderId)
|
||||
throws IOException, ApiException, TolerableFailureException;
|
||||
MailboxFolderId folderId) throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Used by owner and contacts to retrieve a file.
|
||||
@@ -106,22 +102,17 @@ interface MailboxApi {
|
||||
* in the response body.
|
||||
*
|
||||
* @param file the empty file the response bytes will be written into.
|
||||
* @throws TolerableFailureException if response code is 404 (folder does
|
||||
* not exist, client is not authorised to download from folder, or file
|
||||
* does not exist)
|
||||
*/
|
||||
void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
MailboxFileId fileId, File file)
|
||||
throws IOException, ApiException, TolerableFailureException;
|
||||
MailboxFileId fileId, File file) throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Used by owner and contacts to delete files.
|
||||
* <p>
|
||||
* Returns 200 OK (no exception) if deletion was successful.
|
||||
*
|
||||
* @throws TolerableFailureException if response code is 404 (folder does
|
||||
* not exist, client is not authorised to download from folder, or file
|
||||
* does not exist)
|
||||
* @throws TolerableFailureException on 404 response,
|
||||
* because file was most likely deleted already.
|
||||
*/
|
||||
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
MailboxFileId fileId)
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -34,7 +35,6 @@ import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
|
||||
import static java.util.Collections.sort;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static okhttp3.internal.Util.EMPTY_REQUEST;
|
||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||
@@ -42,22 +42,18 @@ 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,
|
||||
UrlConverter urlConverter) {
|
||||
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
|
||||
this.httpClientProvider = httpClientProvider;
|
||||
this.urlConverter = urlConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,7 +78,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(getBaseUrl(properties) + "/setup")
|
||||
.url(properties.getBaseUrl() + "/setup")
|
||||
.put(EMPTY_REQUEST)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
@@ -97,7 +93,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
if (tokenNode == null) {
|
||||
throw new ApiException();
|
||||
}
|
||||
return new MailboxProperties(properties.getOnion(),
|
||||
return new MailboxProperties(properties.getBaseUrl(),
|
||||
MailboxAuthToken.fromString(tokenNode.textValue()),
|
||||
parseServerSupports(node));
|
||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||
@@ -125,8 +121,6 @@ class MailboxApiImpl implements MailboxApi {
|
||||
if (major < 0 || minor < 0) throw new ApiException();
|
||||
serverSupports.add(new MailboxVersion(major, minor));
|
||||
}
|
||||
// Sort the list of versions for easier comparison
|
||||
sort(serverSupports);
|
||||
return serverSupports;
|
||||
}
|
||||
|
||||
@@ -143,7 +137,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(getBaseUrl(properties) + "/")
|
||||
.url(properties.getBaseUrl() + "/")
|
||||
.delete()
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
@@ -168,7 +162,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
public void deleteContact(MailboxProperties properties, ContactId contactId)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
String url = getBaseUrl(properties) + "/contacts/" +
|
||||
String url = properties.getBaseUrl() + "/contacts/" +
|
||||
contactId.getInt();
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.delete()
|
||||
@@ -218,11 +212,9 @@ class MailboxApiImpl implements MailboxApi {
|
||||
|
||||
@Override
|
||||
public List<MailboxFile> getFiles(MailboxProperties properties,
|
||||
MailboxFolderId folderId)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
MailboxFolderId folderId) throws IOException, ApiException {
|
||||
String path = "/files/" + folderId;
|
||||
Response response = sendGetRequest(properties, path);
|
||||
if (response.code() == 404) throw new TolerableFailureException();
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
@@ -247,7 +239,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
if (time < 1) throw new ApiException();
|
||||
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
|
||||
}
|
||||
sort(list);
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||
throw new ApiException();
|
||||
@@ -256,11 +248,9 @@ class MailboxApiImpl implements MailboxApi {
|
||||
|
||||
@Override
|
||||
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
MailboxFileId fileId, File file)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
MailboxFileId fileId, File file) throws IOException, ApiException {
|
||||
String path = "/files/" + folderId + "/" + fileId;
|
||||
Response response = sendGetRequest(properties, path);
|
||||
if (response.code() == 404) throw new TolerableFailureException();
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
@@ -276,7 +266,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
String path = "/files/" + folderId + "/" + fileId;
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.delete()
|
||||
.url(getBaseUrl(properties) + path)
|
||||
.url(properties.getBaseUrl() + path)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
Response response = client.newCall(request).execute();
|
||||
@@ -318,7 +308,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
private Response sendGetRequest(MailboxProperties properties, String path)
|
||||
throws IOException {
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(getBaseUrl(properties) + path)
|
||||
.url(properties.getBaseUrl() + path)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
return client.newCall(request).execute();
|
||||
@@ -327,7 +317,7 @@ class MailboxApiImpl implements MailboxApi {
|
||||
private Response sendPostRequest(MailboxProperties properties, String path,
|
||||
RequestBody body) throws IOException {
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(getBaseUrl(properties) + path)
|
||||
.url(properties.getBaseUrl() + path)
|
||||
.post(body)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
@@ -349,7 +339,4 @@ class MailboxApiImpl implements MailboxApi {
|
||||
return (ArrayNode) arrayNode;
|
||||
}
|
||||
|
||||
private String getBaseUrl(MailboxProperties properties) {
|
||||
return urlConverter.convertOnionToBaseUrl(properties.getOnion());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface MailboxClient {
|
||||
|
||||
/**
|
||||
* Asynchronously starts the client.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Destroys the client and its workers, cancelling any pending tasks or
|
||||
* retries.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Assigns a contact to the client for upload.
|
||||
*
|
||||
* @param properties Properties for communicating with the mailbox
|
||||
* managed by this client.
|
||||
* @param folderId The ID of the folder to which files will be uploaded.
|
||||
*/
|
||||
void assignContactForUpload(ContactId c, MailboxProperties properties,
|
||||
MailboxFolderId folderId);
|
||||
|
||||
/**
|
||||
* Deassigns a contact from the client for upload.
|
||||
*/
|
||||
void deassignContactForUpload(ContactId c);
|
||||
|
||||
/**
|
||||
* Assigns a contact to the client for download.
|
||||
*
|
||||
* @param properties Properties for communicating with the mailbox
|
||||
* managed by this client.
|
||||
* @param folderId The ID of the folder from which files will be
|
||||
* downloaded.
|
||||
*/
|
||||
void assignContactForDownload(ContactId c, MailboxProperties properties,
|
||||
MailboxFolderId folderId);
|
||||
|
||||
/**
|
||||
* Deassigns a contact from the client for download.
|
||||
*/
|
||||
void deassignContactForDownload(ContactId c);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
interface MailboxClientFactory {
|
||||
|
||||
/**
|
||||
* Creates a client for communicating with a contact's mailbox.
|
||||
*/
|
||||
MailboxClient createContactMailboxClient(
|
||||
TorReachabilityMonitor reachabilityMonitor);
|
||||
|
||||
/**
|
||||
* Creates a client for communicating with our own mailbox.
|
||||
*/
|
||||
MailboxClient createOwnMailboxClient(
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
@NotNullByDefault
|
||||
class MailboxClientFactoryImpl implements MailboxClientFactory {
|
||||
|
||||
private final MailboxWorkerFactory workerFactory;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final Executor ioExecutor;
|
||||
private final Provider<ContactMailboxConnectivityChecker>
|
||||
contactCheckerProvider;
|
||||
private final Provider<OwnMailboxConnectivityChecker> ownCheckerProvider;
|
||||
|
||||
@Inject
|
||||
MailboxClientFactoryImpl(MailboxWorkerFactory workerFactory,
|
||||
TaskScheduler taskScheduler,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
Provider<ContactMailboxConnectivityChecker> contactCheckerProvider,
|
||||
Provider<OwnMailboxConnectivityChecker> ownCheckerProvider) {
|
||||
this.workerFactory = workerFactory;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.contactCheckerProvider = contactCheckerProvider;
|
||||
this.ownCheckerProvider = ownCheckerProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxClient createContactMailboxClient(
|
||||
TorReachabilityMonitor reachabilityMonitor) {
|
||||
ConnectivityChecker connectivityChecker = contactCheckerProvider.get();
|
||||
return new ContactMailboxClient(workerFactory, connectivityChecker,
|
||||
reachabilityMonitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxClient createOwnMailboxClient(
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties) {
|
||||
ConnectivityChecker connectivityChecker = ownCheckerProvider.get();
|
||||
return new OwnMailboxClient(workerFactory, connectivityChecker,
|
||||
reachabilityMonitor, taskScheduler, ioExecutor, properties);
|
||||
}
|
||||
}
|
||||
@@ -1,667 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
|
||||
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
|
||||
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
|
||||
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxHelper.isClientCompatibleWithServer;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
/**
|
||||
* This component manages a {@link MailboxClient} for each mailbox we know
|
||||
* about and are able to use (our own mailbox and/or contacts' mailboxes).
|
||||
* The clients are created when we come online (i.e. when the Tor plugin
|
||||
* becomes {@link Plugin.State#ACTIVE active}) and destroyed when we go
|
||||
* offline.
|
||||
* <p/>
|
||||
* The manager keeps track of the latest {@link MailboxUpdate} sent to and
|
||||
* received from each contact. These updates are used to decide which
|
||||
* mailboxes the manager needs clients for, and which mailbox (if any) should
|
||||
* be used for uploading data to and/or downloading data from each contact.
|
||||
* <p/>
|
||||
* The assignments of contacts to mailboxes for upload and/or download may
|
||||
* change when the following events happen:
|
||||
* <ul>
|
||||
* <li> A mailbox is paired or unpaired </li>
|
||||
* <li> A contact is added or removed </li>
|
||||
* <li> A {@link MailboxUpdate} is received from a contact </li>
|
||||
* <li> We discover that our own mailbox's supported API versions have
|
||||
* changed </li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* The manager keeps its mutable state consistent with the state in the DB by
|
||||
* using commit actions and events to update the manager's state on the event
|
||||
* thread.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class MailboxClientManager implements Service, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(MailboxClientManager.class.getName());
|
||||
|
||||
private final Executor eventExecutor, dbExecutor;
|
||||
private final TransactionManager db;
|
||||
private final ContactManager contactManager;
|
||||
private final PluginManager pluginManager;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final MailboxUpdateManager mailboxUpdateManager;
|
||||
private final MailboxClientFactory mailboxClientFactory;
|
||||
private final TorReachabilityMonitor reachabilityMonitor;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
// All of the following mutable state must only be accessed on the
|
||||
// event thread
|
||||
private final Map<ContactId, Updates> contactUpdates = new HashMap<>();
|
||||
private final Map<ContactId, MailboxClient> contactClients =
|
||||
new HashMap<>();
|
||||
@Nullable
|
||||
private MailboxProperties ownProperties = null;
|
||||
@Nullable
|
||||
private MailboxClient ownClient = null;
|
||||
private boolean online = false, handleEvents = false;
|
||||
|
||||
@Inject
|
||||
MailboxClientManager(@EventExecutor Executor eventExecutor,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
TransactionManager db,
|
||||
ContactManager contactManager,
|
||||
PluginManager pluginManager,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxUpdateManager mailboxUpdateManager,
|
||||
MailboxClientFactory mailboxClientFactory,
|
||||
TorReachabilityMonitor reachabilityMonitor) {
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.db = db;
|
||||
this.contactManager = contactManager;
|
||||
this.pluginManager = pluginManager;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||
this.mailboxClientFactory = mailboxClientFactory;
|
||||
this.reachabilityMonitor = reachabilityMonitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
reachabilityMonitor.start();
|
||||
dbExecutor.execute(this::loadMailboxProperties);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void loadMailboxProperties() {
|
||||
LOG.info("Loading mailbox properties");
|
||||
try {
|
||||
db.transaction(true, txn -> {
|
||||
Map<ContactId, Updates> updates = new HashMap<>();
|
||||
for (Contact c : contactManager.getContacts(txn)) {
|
||||
MailboxUpdate local = mailboxUpdateManager
|
||||
.getLocalUpdate(txn, c.getId());
|
||||
MailboxUpdate remote = mailboxUpdateManager
|
||||
.getRemoteUpdate(txn, c.getId());
|
||||
updates.put(c.getId(), new Updates(local, remote));
|
||||
}
|
||||
MailboxProperties ownProps =
|
||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
||||
// Use a commit action so the state in memory remains
|
||||
// consistent with the state in the DB
|
||||
txn.attach(() -> initialiseState(updates, ownProps));
|
||||
});
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void initialiseState(Map<ContactId, Updates> updates,
|
||||
@Nullable MailboxProperties ownProps) {
|
||||
contactUpdates.putAll(updates);
|
||||
ownProperties = ownProps;
|
||||
Plugin tor = pluginManager.getPlugin(ID);
|
||||
if (tor != null && tor.getState() == ACTIVE) {
|
||||
LOG.info("Online");
|
||||
online = true;
|
||||
createClients();
|
||||
}
|
||||
// Now that the mutable state has been initialised we can start
|
||||
// handling events. This is done in a commit action so that we don't
|
||||
// miss any changes to the DB state or handle events for any changes
|
||||
// that were already reflected in the initial load
|
||||
handleEvents = true;
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void createClients() {
|
||||
LOG.info("Creating clients");
|
||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
Updates u = e.getValue();
|
||||
if (isContactMailboxUsable(u.remote)) {
|
||||
// Create and start a client for the contact's mailbox
|
||||
MailboxClient contactClient = createAndStartClient(c);
|
||||
// Assign the contact to the contact's mailbox for upload
|
||||
assignContactToContactMailboxForUpload(c, contactClient, u);
|
||||
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
|
||||
// We don't have a usable mailbox, so assign the contact to
|
||||
// the contact's mailbox for download too
|
||||
assignContactToContactMailboxForDownload(c,
|
||||
contactClient, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ownProperties == null) return;
|
||||
if (!isOwnMailboxUsable(ownProperties)) {
|
||||
LOG.warning("We have a mailbox but we can't use it");
|
||||
return;
|
||||
}
|
||||
// Create and start a client for our mailbox
|
||||
createAndStartClientForOwnMailbox();
|
||||
// Assign contacts to our mailbox for upload/download
|
||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
Updates u = e.getValue();
|
||||
if (isOwnMailboxUsable(ownProperties, u.remote)) {
|
||||
// Assign the contact to our mailbox for download
|
||||
assignContactToOwnMailboxForDownload(c, u);
|
||||
if (!isContactMailboxUsable(u.remote)) {
|
||||
// The contact doesn't have a usable mailbox, so assign
|
||||
// the contact to our mailbox for upload too
|
||||
assignContactToOwnMailboxForUpload(c, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
eventExecutor.execute(() -> {
|
||||
handleEvents = false;
|
||||
if (online) destroyClients();
|
||||
latch.countDown();
|
||||
});
|
||||
reachabilityMonitor.destroy();
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void destroyClients() {
|
||||
LOG.info("Destroying clients");
|
||||
for (MailboxClient client : contactClients.values()) {
|
||||
client.destroy();
|
||||
}
|
||||
contactClients.clear();
|
||||
destroyOwnClient();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void destroyOwnClient() {
|
||||
if (ownClient != null) {
|
||||
ownClient.destroy();
|
||||
ownClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (!handleEvents) return;
|
||||
if (e instanceof TransportActiveEvent) {
|
||||
TransportActiveEvent t = (TransportActiveEvent) e;
|
||||
if (t.getTransportId().equals(ID)) onTorActive();
|
||||
} else if (e instanceof TransportInactiveEvent) {
|
||||
TransportInactiveEvent t = (TransportInactiveEvent) e;
|
||||
if (t.getTransportId().equals(ID)) onTorInactive();
|
||||
} else if (e instanceof MailboxPairedEvent) {
|
||||
LOG.info("Mailbox paired");
|
||||
MailboxPairedEvent m = (MailboxPairedEvent) e;
|
||||
onMailboxPaired(m.getProperties(), m.getLocalUpdates());
|
||||
} else if (e instanceof MailboxUnpairedEvent) {
|
||||
LOG.info("Mailbox unpaired");
|
||||
MailboxUnpairedEvent m = (MailboxUnpairedEvent) e;
|
||||
onMailboxUnpaired(m.getLocalUpdates());
|
||||
} else if (e instanceof MailboxUpdateSentToNewContactEvent) {
|
||||
LOG.info("Contact added");
|
||||
MailboxUpdateSentToNewContactEvent
|
||||
m = (MailboxUpdateSentToNewContactEvent) e;
|
||||
onContactAdded(m.getContactId(), m.getMailboxUpdate());
|
||||
} else if (e instanceof ContactRemovedEvent) {
|
||||
LOG.info("Contact removed");
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
onContactRemoved(c.getContactId());
|
||||
} else if (e instanceof RemoteMailboxUpdateEvent) {
|
||||
LOG.info("Remote mailbox update");
|
||||
RemoteMailboxUpdateEvent r = (RemoteMailboxUpdateEvent) e;
|
||||
onRemoteMailboxUpdate(r.getContact(), r.getMailboxUpdate());
|
||||
} else if (e instanceof OwnMailboxConnectionStatusEvent) {
|
||||
OwnMailboxConnectionStatusEvent o =
|
||||
(OwnMailboxConnectionStatusEvent) e;
|
||||
onOwnMailboxConnectionStatusChanged(o.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onTorActive() {
|
||||
// If we checked the plugin at startup concurrently with the plugin
|
||||
// becoming active then `online` may already be true when we receive
|
||||
// the first TransportActiveEvent, in which case ignore it
|
||||
if (online) return;
|
||||
LOG.info("Online");
|
||||
online = true;
|
||||
createClients();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onTorInactive() {
|
||||
// If we checked the plugin at startup concurrently with the plugin
|
||||
// becoming inactive then `online` may already be false when we
|
||||
// receive the first TransportInactiveEvent, in which case ignore it
|
||||
if (!online) return;
|
||||
LOG.info("Offline");
|
||||
online = false;
|
||||
destroyClients();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onMailboxPaired(MailboxProperties ownProps,
|
||||
Map<ContactId, MailboxUpdateWithMailbox> localUpdates) {
|
||||
for (Entry<ContactId, MailboxUpdateWithMailbox> e :
|
||||
localUpdates.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
Updates u = contactUpdates.get(c);
|
||||
contactUpdates.put(c, new Updates(e.getValue(), u.remote));
|
||||
}
|
||||
ownProperties = ownProps;
|
||||
if (!online) return;
|
||||
if (!isOwnMailboxUsable(ownProperties)) {
|
||||
LOG.warning("We have a mailbox but we can't use it");
|
||||
return;
|
||||
}
|
||||
// Create and start a client for our mailbox
|
||||
createAndStartClientForOwnMailbox();
|
||||
// Assign contacts to our mailbox for upload/download
|
||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
Updates u = e.getValue();
|
||||
if (!isOwnMailboxUsable(ownProperties, u.remote)) {
|
||||
// Our mailbox isn't usable for communicating with this
|
||||
// contact, so don't assign/reassign this contact
|
||||
continue;
|
||||
}
|
||||
if (isContactMailboxUsable(u.remote)) {
|
||||
// The contact has a usable mailbox, so the contact should
|
||||
// currently be assigned to the contact's mailbox for upload
|
||||
// and download. Reassign the contact to our mailbox for
|
||||
// download
|
||||
MailboxClient contactClient =
|
||||
requireNonNull(contactClients.get(c));
|
||||
contactClient.deassignContactForDownload(c);
|
||||
} else {
|
||||
// The contact doesn't have a usable mailbox, so assign the
|
||||
// contact to our mailbox for upload
|
||||
assignContactToOwnMailboxForUpload(c, u);
|
||||
}
|
||||
assignContactToOwnMailboxForDownload(c, u);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onMailboxUnpaired(Map<ContactId, MailboxUpdate> localUpdates) {
|
||||
for (Entry<ContactId, MailboxUpdate> e : localUpdates.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
Updates updates = contactUpdates.get(c);
|
||||
contactUpdates.put(c, new Updates(e.getValue(), updates.remote));
|
||||
}
|
||||
MailboxProperties oldOwnProperties = ownProperties;
|
||||
ownProperties = null;
|
||||
if (!online) return;
|
||||
// Destroy the client for our own mailbox, if any
|
||||
destroyOwnClient();
|
||||
// Reassign contacts to their own mailboxes for download where possible
|
||||
for (Entry<ContactId, Updates> e : contactUpdates.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
Updates u = e.getValue();
|
||||
if (isContactMailboxUsable(u.remote) &&
|
||||
isOwnMailboxUsable(oldOwnProperties, u.remote)) {
|
||||
// The contact should currently be assigned to our mailbox
|
||||
// for download. Reassign the contact to the contact's
|
||||
// mailbox for download
|
||||
MailboxClient contactClient =
|
||||
requireNonNull(contactClients.get(c));
|
||||
assignContactToContactMailboxForDownload(c, contactClient, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactAdded(ContactId c, MailboxUpdate u) {
|
||||
Updates old = contactUpdates.put(c, new Updates(u, null));
|
||||
if (old != null) throw new IllegalStateException();
|
||||
// We haven't yet received an update from the newly added contact,
|
||||
// so at this stage we don't need to assign the contact to any
|
||||
// mailboxes for upload or download
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactRemoved(ContactId c) {
|
||||
Updates updates = requireNonNull(contactUpdates.remove(c));
|
||||
if (!online) return;
|
||||
// Destroy the client for the contact's mailbox, if any
|
||||
MailboxClient client = contactClients.remove(c);
|
||||
if (client != null) client.destroy();
|
||||
// If we have a mailbox and the contact is assigned to it for upload
|
||||
// and/or download, deassign the contact
|
||||
if (ownProperties == null) return;
|
||||
if (isOwnMailboxUsable(ownProperties, updates.remote)) {
|
||||
// We have a usable mailbox, so the contact should currently be
|
||||
// assigned to our mailbox for download. Deassign the contact from
|
||||
// our mailbox for download
|
||||
requireNonNull(ownClient).deassignContactForDownload(c);
|
||||
if (!isContactMailboxUsable(updates.remote)) {
|
||||
// The contact doesn't have a usable mailbox, so the contact
|
||||
// should currently be assigned to our mailbox for upload.
|
||||
// Deassign the contact from our mailbox for upload
|
||||
requireNonNull(ownClient).deassignContactForUpload(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onRemoteMailboxUpdate(ContactId c, MailboxUpdate remote) {
|
||||
Updates old = contactUpdates.get(c);
|
||||
MailboxUpdate oldRemote = old.remote;
|
||||
Updates u = new Updates(old.local, remote);
|
||||
contactUpdates.put(c, u);
|
||||
if (!online) return;
|
||||
// What may have changed?
|
||||
// * Contact's mailbox may be usable now, was unusable before
|
||||
// * Contact's mailbox may be unusable now, was usable before
|
||||
// * Contact's mailbox may have been replaced
|
||||
// * Contact's mailbox may have changed its API versions
|
||||
// * Contact may be able to use our mailbox now, was unable before
|
||||
// * Contact may be unable to use our mailbox now, was able before
|
||||
boolean wasContactMailboxUsable = isContactMailboxUsable(oldRemote);
|
||||
boolean isContactMailboxUsable = isContactMailboxUsable(remote);
|
||||
boolean wasOwnMailboxUsable =
|
||||
isOwnMailboxUsable(ownProperties, oldRemote);
|
||||
boolean isOwnMailboxUsable = isOwnMailboxUsable(ownProperties, remote);
|
||||
|
||||
// Create/destroy/replace the client for the contact's mailbox if needed
|
||||
MailboxClient contactClient = null;
|
||||
boolean clientReplaced = false;
|
||||
if (isContactMailboxUsable) {
|
||||
if (wasContactMailboxUsable) {
|
||||
MailboxProperties oldProps = getMailboxProperties(oldRemote);
|
||||
MailboxProperties newProps = getMailboxProperties(remote);
|
||||
if (oldProps.equals(newProps)) {
|
||||
// The contact previously had a usable mailbox, now has
|
||||
// a usable mailbox, it's the same mailbox, and its API
|
||||
// versions haven't changed. Keep using the existing client
|
||||
contactClient = requireNonNull(contactClients.get(c));
|
||||
} else {
|
||||
// The contact previously had a usable mailbox and now has
|
||||
// a usable mailbox, but either it's a new mailbox or its
|
||||
// API versions have changed. Replace the client
|
||||
requireNonNull(contactClients.remove(c)).destroy();
|
||||
contactClient = createAndStartClient(c);
|
||||
clientReplaced = true;
|
||||
}
|
||||
} else {
|
||||
// The client didn't previously have a usable mailbox but now
|
||||
// has one. Create and start a client
|
||||
contactClient = createAndStartClient(c);
|
||||
}
|
||||
} else if (wasContactMailboxUsable) {
|
||||
// The client previously had a usable mailbox but no longer does.
|
||||
// Destroy the existing client
|
||||
requireNonNull(contactClients.remove(c)).destroy();
|
||||
}
|
||||
|
||||
boolean wasAssignedToOwnMailboxForUpload =
|
||||
wasOwnMailboxUsable && !wasContactMailboxUsable;
|
||||
boolean willBeAssignedToOwnMailboxForUpload =
|
||||
isOwnMailboxUsable && !isContactMailboxUsable;
|
||||
|
||||
boolean wasAssignedToContactMailboxForDownload =
|
||||
!wasOwnMailboxUsable && wasContactMailboxUsable;
|
||||
boolean willBeAssignedToContactMailboxForDownload =
|
||||
!isOwnMailboxUsable && isContactMailboxUsable;
|
||||
|
||||
// Deassign the contact for upload/download if needed
|
||||
if (wasAssignedToOwnMailboxForUpload &&
|
||||
!willBeAssignedToOwnMailboxForUpload) {
|
||||
requireNonNull(ownClient).deassignContactForUpload(c);
|
||||
}
|
||||
if (wasOwnMailboxUsable && !isOwnMailboxUsable) {
|
||||
requireNonNull(ownClient).deassignContactForDownload(c);
|
||||
}
|
||||
// If the client for the contact's mailbox was replaced or destroyed
|
||||
// above then we don't need to deassign the contact for download
|
||||
if (wasAssignedToContactMailboxForDownload &&
|
||||
!willBeAssignedToContactMailboxForDownload &&
|
||||
!clientReplaced && isContactMailboxUsable) {
|
||||
requireNonNull(contactClient).deassignContactForDownload(c);
|
||||
}
|
||||
// We never need to deassign the contact from the contact's mailbox for
|
||||
// upload: this would only be needed if the contact's mailbox were no
|
||||
// longer usable, in which case the client would already have been
|
||||
// destroyed above. Thanks to the linter for spotting this
|
||||
|
||||
// Assign the contact for upload/download if needed
|
||||
if (!wasAssignedToOwnMailboxForUpload &&
|
||||
willBeAssignedToOwnMailboxForUpload) {
|
||||
assignContactToOwnMailboxForUpload(c, u);
|
||||
}
|
||||
if (!wasOwnMailboxUsable && isOwnMailboxUsable) {
|
||||
assignContactToOwnMailboxForDownload(c, u);
|
||||
}
|
||||
if ((!wasContactMailboxUsable || clientReplaced) &&
|
||||
isContactMailboxUsable) {
|
||||
assignContactToContactMailboxForUpload(c, contactClient, u);
|
||||
}
|
||||
if ((!wasAssignedToContactMailboxForDownload || clientReplaced) &&
|
||||
willBeAssignedToContactMailboxForDownload) {
|
||||
assignContactToContactMailboxForDownload(c, contactClient, u);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onOwnMailboxConnectionStatusChanged(MailboxStatus status) {
|
||||
if (!online || ownProperties == null) return;
|
||||
List<MailboxVersion> oldServerSupports =
|
||||
ownProperties.getServerSupports();
|
||||
List<MailboxVersion> newServerSupports = status.getServerSupports();
|
||||
if (!oldServerSupports.equals(newServerSupports)) {
|
||||
LOG.info("Our mailbox's supported API versions have changed");
|
||||
// This potentially affects every assignment of contacts to
|
||||
// mailboxes for upload and download, so just rebuild the clients
|
||||
// and assignments from scratch
|
||||
destroyClients();
|
||||
createClients();
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void createAndStartClientForOwnMailbox() {
|
||||
if (ownClient != null) throw new IllegalStateException();
|
||||
ownClient = mailboxClientFactory.createOwnMailboxClient(
|
||||
reachabilityMonitor, requireNonNull(ownProperties));
|
||||
ownClient.start();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private MailboxClient createAndStartClient(ContactId c) {
|
||||
MailboxClient client = mailboxClientFactory
|
||||
.createContactMailboxClient(reachabilityMonitor);
|
||||
MailboxClient old = contactClients.put(c, client);
|
||||
if (old != null) throw new IllegalStateException();
|
||||
client.start();
|
||||
return client;
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void assignContactToOwnMailboxForDownload(ContactId c, Updates u) {
|
||||
MailboxProperties localProps = getMailboxProperties(u.local);
|
||||
requireNonNull(ownClient).assignContactForDownload(c,
|
||||
requireNonNull(ownProperties),
|
||||
requireNonNull(localProps.getOutboxId()));
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void assignContactToOwnMailboxForUpload(ContactId c, Updates u) {
|
||||
MailboxProperties localProps = getMailboxProperties(u.local);
|
||||
requireNonNull(ownClient).assignContactForUpload(c,
|
||||
requireNonNull(ownProperties),
|
||||
requireNonNull(localProps.getInboxId()));
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void assignContactToContactMailboxForDownload(ContactId c,
|
||||
MailboxClient contactClient, Updates u) {
|
||||
MailboxProperties remoteProps =
|
||||
getMailboxProperties(requireNonNull(u.remote));
|
||||
contactClient.assignContactForDownload(c, remoteProps,
|
||||
requireNonNull(remoteProps.getInboxId()));
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void assignContactToContactMailboxForUpload(ContactId c,
|
||||
MailboxClient contactClient, Updates u) {
|
||||
MailboxProperties remoteProps =
|
||||
getMailboxProperties(requireNonNull(u.remote));
|
||||
contactClient.assignContactForUpload(c, remoteProps,
|
||||
requireNonNull(remoteProps.getOutboxId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MailboxProperties} included in the given update,
|
||||
* which must be a {@link MailboxUpdateWithMailbox}.
|
||||
*/
|
||||
private MailboxProperties getMailboxProperties(MailboxUpdate update) {
|
||||
if (!(update instanceof MailboxUpdateWithMailbox)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
MailboxUpdateWithMailbox mailbox = (MailboxUpdateWithMailbox) update;
|
||||
return mailbox.getMailboxProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we can use our own mailbox to communicate with the
|
||||
* contact that sent the given update.
|
||||
*/
|
||||
private boolean isOwnMailboxUsable(
|
||||
@Nullable MailboxProperties ownProperties,
|
||||
@Nullable MailboxUpdate remote) {
|
||||
if (ownProperties == null || remote == null) return false;
|
||||
return isMailboxUsable(remote.getClientSupports(),
|
||||
ownProperties.getServerSupports());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we can use the contact's mailbox to communicate with
|
||||
* the contact that sent the given update.
|
||||
*/
|
||||
private boolean isContactMailboxUsable(@Nullable MailboxUpdate remote) {
|
||||
if (remote instanceof MailboxUpdateWithMailbox) {
|
||||
MailboxUpdateWithMailbox remoteMailbox =
|
||||
(MailboxUpdateWithMailbox) remote;
|
||||
return isMailboxUsable(remoteMailbox.getClientSupports(),
|
||||
remoteMailbox.getMailboxProperties().getServerSupports());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we can communicate with a contact that has the given
|
||||
* client-supported API versions via a mailbox with the given
|
||||
* server-supported API versions.
|
||||
*/
|
||||
private boolean isMailboxUsable(List<MailboxVersion> contactClient,
|
||||
List<MailboxVersion> server) {
|
||||
return isClientCompatibleWithServer(contactClient, server)
|
||||
&& isClientCompatibleWithServer(CLIENT_SUPPORTS, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if our client-supported API versions are compatible with
|
||||
* our own mailbox's server-supported API versions.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean isOwnMailboxUsable(MailboxProperties ownProperties) {
|
||||
return isClientCompatibleWithServer(CLIENT_SUPPORTS,
|
||||
ownProperties.getServerSupports());
|
||||
}
|
||||
|
||||
/**
|
||||
* A container for the latest {@link MailboxUpdate updates} sent to and
|
||||
* received from a given contact.
|
||||
*/
|
||||
private static class Updates {
|
||||
|
||||
/**
|
||||
* The latest update sent to the contact.
|
||||
*/
|
||||
private final MailboxUpdate local;
|
||||
|
||||
/**
|
||||
* The latest update received from the contact, or null if no update
|
||||
* has been received.
|
||||
*/
|
||||
@Nullable
|
||||
private final MailboxUpdate remote;
|
||||
|
||||
private Updates(MailboxUpdate local, @Nullable MailboxUpdate remote) {
|
||||
this.local = local;
|
||||
this.remote = remote;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Queue;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
abstract class MailboxDownloadWorker implements MailboxWorker,
|
||||
ConnectivityObserver, TorReachabilityObserver {
|
||||
|
||||
/**
|
||||
* When the worker is started it waits for a connectivity check, then
|
||||
* starts its first download cycle: checking for files to download,
|
||||
* downloading and deleting the files, and checking again until all files
|
||||
* have been downloaded and deleted.
|
||||
* <p>
|
||||
* The worker then waits for our Tor hidden service to be reachable before
|
||||
* starting its second download cycle. This ensures that if a contact
|
||||
* tried and failed to connect to our hidden service before it was
|
||||
* reachable, and therefore uploaded a file to the mailbox instead, we'll
|
||||
* find the file in the second download cycle.
|
||||
*/
|
||||
protected enum State {
|
||||
CREATED,
|
||||
CONNECTIVITY_CHECK,
|
||||
DOWNLOAD_CYCLE_1,
|
||||
WAITING_FOR_TOR,
|
||||
DOWNLOAD_CYCLE_2,
|
||||
FINISHED,
|
||||
DESTROYED
|
||||
}
|
||||
|
||||
protected static final Logger LOG =
|
||||
getLogger(MailboxDownloadWorker.class.getName());
|
||||
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final TorReachabilityMonitor torReachabilityMonitor;
|
||||
protected final MailboxApiCaller mailboxApiCaller;
|
||||
protected final MailboxApi mailboxApi;
|
||||
private final MailboxFileManager mailboxFileManager;
|
||||
protected final MailboxProperties mailboxProperties;
|
||||
protected final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
protected State state = State.CREATED;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
protected Cancellable apiCall = null;
|
||||
|
||||
/**
|
||||
* Creates the API call that starts the worker's download cycle.
|
||||
*/
|
||||
protected abstract ApiCall createApiCallForDownloadCycle();
|
||||
|
||||
MailboxDownloadWorker(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor torReachabilityMonitor,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager,
|
||||
MailboxProperties mailboxProperties) {
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.torReachabilityMonitor = torReachabilityMonitor;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxFileManager = mailboxFileManager;
|
||||
this.mailboxProperties = mailboxProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
synchronized (lock) {
|
||||
// Don't allow the worker to be reused
|
||||
if (state != State.CREATED) return;
|
||||
state = State.CONNECTIVITY_CHECK;
|
||||
}
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
Cancellable apiCall;
|
||||
synchronized (lock) {
|
||||
state = State.DESTROYED;
|
||||
apiCall = this.apiCall;
|
||||
this.apiCall = null;
|
||||
}
|
||||
if (apiCall != null) apiCall.cancel();
|
||||
connectivityChecker.removeObserver(this);
|
||||
torReachabilityMonitor.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
LOG.info("Connectivity check succeeded");
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTIVITY_CHECK) return;
|
||||
state = State.DOWNLOAD_CYCLE_1;
|
||||
// Start first download cycle
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
createApiCallForDownloadCycle());
|
||||
}
|
||||
}
|
||||
|
||||
void onDownloadCycleFinished() {
|
||||
boolean addObserver = false;
|
||||
synchronized (lock) {
|
||||
if (state == State.DOWNLOAD_CYCLE_1) {
|
||||
LOG.info("First download cycle finished");
|
||||
state = State.WAITING_FOR_TOR;
|
||||
apiCall = null;
|
||||
addObserver = true;
|
||||
} else if (state == State.DOWNLOAD_CYCLE_2) {
|
||||
LOG.info("Second download cycle finished");
|
||||
state = State.FINISHED;
|
||||
apiCall = null;
|
||||
}
|
||||
}
|
||||
if (addObserver) {
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
torReachabilityMonitor.addOneShotObserver(this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) torReachabilityMonitor.removeObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
void downloadNextFile(Queue<FolderFile> queue) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
if (queue.isEmpty()) {
|
||||
// Check for files again, as new files may have arrived while
|
||||
// we were downloading
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
createApiCallForDownloadCycle());
|
||||
} else {
|
||||
FolderFile file = queue.remove();
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() ->
|
||||
apiCallDownloadFile(file, queue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallDownloadFile(FolderFile file, Queue<FolderFile> queue)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
LOG.info("Downloading file");
|
||||
File tempFile = mailboxFileManager.createTempFileForDownload();
|
||||
try {
|
||||
mailboxApi.getFile(mailboxProperties, file.folderId, file.fileId,
|
||||
tempFile);
|
||||
} catch (IOException | ApiException e) {
|
||||
if (!tempFile.delete()) {
|
||||
LOG.warning("Failed to delete temporary file");
|
||||
}
|
||||
throw e;
|
||||
} catch (TolerableFailureException e) {
|
||||
// File not found - continue to the next file
|
||||
LOG.warning("File does not exist");
|
||||
if (!tempFile.delete()) {
|
||||
LOG.warning("Failed to delete temporary file");
|
||||
}
|
||||
downloadNextFile(queue);
|
||||
return;
|
||||
}
|
||||
mailboxFileManager.handleDownloadedFile(tempFile);
|
||||
deleteFile(file, queue);
|
||||
}
|
||||
|
||||
private void deleteFile(FolderFile file, Queue<FolderFile> queue) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallDeleteFile(FolderFile file, Queue<FolderFile> queue)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
try {
|
||||
mailboxApi.deleteFile(mailboxProperties, file.folderId,
|
||||
file.fileId);
|
||||
} catch (TolerableFailureException e) {
|
||||
// File not found - continue to the next file
|
||||
LOG.warning("File does not exist");
|
||||
}
|
||||
downloadNextFile(queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTorReachable() {
|
||||
LOG.info("Our Tor hidden service is reachable");
|
||||
synchronized (lock) {
|
||||
if (state != State.WAITING_FOR_TOR) return;
|
||||
state = State.DOWNLOAD_CYCLE_2;
|
||||
// Start second download cycle
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
createApiCallForDownloadCycle());
|
||||
}
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
static class FolderFile {
|
||||
|
||||
final MailboxFolderId folderId;
|
||||
final MailboxFileId fileId;
|
||||
|
||||
FolderFile(MailboxFolderId folderId, MailboxFileId fileId) {
|
||||
this.folderId = folderId;
|
||||
this.fileId = fileId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
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;
|
||||
@@ -18,14 +16,6 @@ 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()}.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -11,18 +10,13 @@ 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;
|
||||
@@ -36,7 +30,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
|
||||
import static org.briarproject.bramble.api.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
|
||||
@@ -48,7 +41,6 @@ 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;
|
||||
@@ -75,44 +67,14 @@ 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 dir = createDirectoryIfNeeded(dirName);
|
||||
return File.createTempFile("mailbox", ".tmp", dir);
|
||||
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
|
||||
return File.createTempFile("mailbox", ".tmp", downloadDir);
|
||||
}
|
||||
|
||||
private File createDirectoryIfNeeded(String name) throws IOException {
|
||||
@@ -154,8 +116,6 @@ 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)) {
|
||||
@@ -167,25 +127,17 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
||||
|
||||
/**
|
||||
* This method is called at startup, as soon as the plugin is started, to
|
||||
* delete any files that were left in the upload directory at the last
|
||||
* shutdown and handle any files that were left in the download directory.
|
||||
* handle any files that were left in the download directory at the last
|
||||
* shutdown.
|
||||
*/
|
||||
@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[] orphanedDownloads = downloadDir.listFiles();
|
||||
// Now that we've got the list of orphaned downloads, new files
|
||||
// can be created in the download directory
|
||||
File[] orphans = downloadDir.listFiles();
|
||||
// Now that we've got the list of orphans, new files can be created
|
||||
orphanLatch.countDown();
|
||||
if (orphanedDownloads != null) {
|
||||
for (File f : orphanedDownloads) handleDownloadedFile(f);
|
||||
}
|
||||
if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
@@ -213,58 +165,9 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
|
||||
delegate.dispose(exception, recognised);
|
||||
if (isHandlingComplete(exception, recognised)) {
|
||||
LOG.info("Deleting 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;
|
||||
if (!file.delete()) {
|
||||
LOG.warning("Failed to delete downloaded file");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@ import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.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;
|
||||
|
||||
@@ -100,35 +98,34 @@ class MailboxManagerImpl implements MailboxManager {
|
||||
|
||||
@Override
|
||||
public boolean checkConnection() {
|
||||
List<MailboxVersion> versions = null;
|
||||
boolean success;
|
||||
try {
|
||||
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||
mailboxSettingsManager::getOwnMailboxProperties);
|
||||
if (props == null) throw new DbException();
|
||||
versions = api.getServerSupports(props);
|
||||
success = api.checkStatus(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(versions);
|
||||
recordCheckResult(success);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
return versions != null;
|
||||
return success;
|
||||
}
|
||||
|
||||
private void recordCheckResult(@Nullable List<MailboxVersion> versions)
|
||||
throws DbException {
|
||||
private void recordCheckResult(boolean success) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> {
|
||||
if (versions != null) {
|
||||
mailboxSettingsManager
|
||||
.recordSuccessfulConnection(txn, now, versions);
|
||||
if (success) {
|
||||
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
|
||||
} else {
|
||||
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
|
||||
}
|
||||
|
||||
@@ -4,22 +4,17 @@ import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -42,8 +37,6 @@ public class MailboxModule {
|
||||
MailboxUpdateManager mailboxUpdateManager;
|
||||
@Inject
|
||||
MailboxFileManager mailboxFileManager;
|
||||
@Inject
|
||||
MailboxClientManager mailboxClientManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -59,19 +52,13 @@ public class MailboxModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxSettingsManager provideMailboxSettingsManager(
|
||||
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
||||
return mailboxSettingsManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
UrlConverter provideUrlConverter(UrlConverterImpl urlConverter) {
|
||||
return urlConverter;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxApi provideMailboxApi(MailboxApiImpl mailboxApi) {
|
||||
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
|
||||
return mailboxApi;
|
||||
}
|
||||
|
||||
@@ -127,55 +114,4 @@ public class MailboxModule {
|
||||
}
|
||||
return mailboxFileManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxWorkerFactory provideMailboxWorkerFactory(
|
||||
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
|
||||
return mailboxWorkerFactory;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxClientFactory provideMailboxClientFactory(
|
||||
MailboxClientFactoryImpl mailboxClientFactory) {
|
||||
return mailboxClientFactory;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxApiCaller provideMailboxApiCaller(
|
||||
MailboxApiCallerImpl mailboxApiCaller) {
|
||||
return mailboxApiCaller;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
TorReachabilityMonitor provideTorReachabilityMonitor(
|
||||
TorReachabilityMonitorImpl reachabilityMonitor) {
|
||||
return reachabilityMonitor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxClientManager provideMailboxClientManager(
|
||||
@EventExecutor Executor eventExecutor,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
TransactionManager db,
|
||||
ContactManager contactManager,
|
||||
PluginManager pluginManager,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxUpdateManager mailboxUpdateManager,
|
||||
MailboxClientFactory mailboxClientFactory,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
FeatureFlags featureFlags,
|
||||
LifecycleManager lifecycleManager,
|
||||
EventBus eventBus) {
|
||||
MailboxClientManager manager = new MailboxClientManager(eventExecutor,
|
||||
dbExecutor, db, contactManager, pluginManager,
|
||||
mailboxSettingsManager, mailboxUpdateManager,
|
||||
mailboxClientFactory, reachabilityMonitor);
|
||||
if (featureFlags.shouldEnableMailbox()) {
|
||||
lifecycleManager.registerService(manager);
|
||||
eventBus.addListener(manager);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +120,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
|
||||
db.transaction(false, txn -> {
|
||||
mailboxSettingsManager
|
||||
.setOwnMailboxProperties(txn, ownerProperties);
|
||||
mailboxSettingsManager.recordSuccessfulConnection(txn, time,
|
||||
ownerProperties.getServerSupports());
|
||||
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
|
||||
// A (possibly new) mailbox is paired. Reset message retransmission
|
||||
// timers for contacts who doesn't have their own mailbox. This way,
|
||||
// data stranded on our old mailbox will be re-uploaded to our new.
|
||||
@@ -178,9 +177,10 @@ 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(onion, setupToken, new ArrayList<>());
|
||||
return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@ThreadSafe
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
|
||||
@@ -74,13 +74,19 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||
throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.put(SETTINGS_KEY_ONION, p.getOnion());
|
||||
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
||||
List<MailboxVersion> serverSupports = p.getServerSupports();
|
||||
encodeServerSupports(serverSupports, s);
|
||||
int[] ints = new int[serverSupports.size() * 2];
|
||||
int i = 0;
|
||||
for (MailboxVersion v : serverSupports) {
|
||||
ints[i++] = v.getMajor();
|
||||
ints[i++] = v.getMinor();
|
||||
}
|
||||
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
for (MailboxHook hook : hooks) {
|
||||
hook.mailboxPaired(txn, p);
|
||||
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,10 +95,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
Settings s = new Settings();
|
||||
s.put(SETTINGS_KEY_ONION, "");
|
||||
s.put(SETTINGS_KEY_TOKEN, "");
|
||||
s.put(SETTINGS_KEY_ATTEMPTS, "");
|
||||
s.put(SETTINGS_KEY_LAST_ATTEMPT, "");
|
||||
s.put(SETTINGS_KEY_LAST_SUCCESS, "");
|
||||
s.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
for (MailboxHook hook : hooks) {
|
||||
hook.mailboxUnpaired(txn);
|
||||
@@ -117,38 +119,25 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordSuccessfulConnection(Transaction txn, long now,
|
||||
List<MailboxVersion> versions) throws DbException {
|
||||
// if we no longer have a paired mailbox, return
|
||||
public void recordSuccessfulConnection(Transaction txn, long now)
|
||||
throws DbException {
|
||||
Settings oldSettings =
|
||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||
String onion = oldSettings.get(SETTINGS_KEY_ONION);
|
||||
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
|
||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
|
||||
Settings s = new Settings();
|
||||
// record the successful connection
|
||||
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
encodeServerSupports(versions, s);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
for (MailboxHook hook : hooks) {
|
||||
hook.serverSupportedVersionsReceived(txn, versions);
|
||||
}
|
||||
// broadcast status event
|
||||
MailboxStatus status = new MailboxStatus(now, now, 0, versions);
|
||||
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
|
||||
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
|
||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||
throws DbException {
|
||||
// if we no longer have a paired mailbox, return
|
||||
Settings oldSettings =
|
||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||
String onion = oldSettings.get(SETTINGS_KEY_ONION);
|
||||
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
|
||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
|
||||
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
|
||||
Settings newSettings = new Settings();
|
||||
@@ -182,17 +171,6 @@ 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();
|
||||
|
||||
@@ -25,9 +25,6 @@ import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxVersion;
|
||||
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
|
||||
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
|
||||
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
|
||||
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
@@ -41,7 +38,6 @@ import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -49,9 +45,6 @@ import java.util.Map.Entry;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -123,7 +116,7 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
addingContact(txn, c, false);
|
||||
addingContact(txn, c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,17 +132,6 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
addingContact(txn, c, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attachEvent True if a {@link MailboxUpdateSentToNewContactEvent}
|
||||
* should be attached to the transaction. We should only do this when
|
||||
* adding a new contact, not when setting up this client for an existing
|
||||
* contact.
|
||||
*/
|
||||
private void addingContact(Transaction txn, Contact c, boolean attachEvent)
|
||||
throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
db.addGroup(txn, g);
|
||||
@@ -161,17 +143,13 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||
MailboxProperties ownProps =
|
||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
||||
MailboxUpdate u;
|
||||
if (ownProps != null) {
|
||||
// We are paired, create and send props to the newly added contact
|
||||
u = createAndSendUpdateWithMailbox(txn, c,
|
||||
ownProps.getServerSupports(), ownProps.getOnion());
|
||||
createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
|
||||
ownProps.getOnion());
|
||||
} else {
|
||||
// Not paired, but we still want to get our clientSupports sent
|
||||
u = sendUpdateNoMailbox(txn, c);
|
||||
}
|
||||
if (attachEvent) {
|
||||
txn.attach(new MailboxUpdateSentToNewContactEvent(c.getId(), u));
|
||||
sendUpdateNoMailbox(txn, c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,103 +159,18 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mailboxPaired(Transaction txn, MailboxProperties p)
|
||||
throws DbException {
|
||||
Map<ContactId, MailboxUpdateWithMailbox> localUpdates = new HashMap<>();
|
||||
public void mailboxPaired(Transaction txn, String ownOnion,
|
||||
List<MailboxVersion> serverSupports) throws DbException {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
MailboxUpdateWithMailbox u = createAndSendUpdateWithMailbox(txn, c,
|
||||
p.getServerSupports(), p.getOnion());
|
||||
localUpdates.put(c.getId(), u);
|
||||
}
|
||||
txn.attach(new MailboxPairedEvent(p, localUpdates));
|
||||
// Store the list of server-supported versions
|
||||
try {
|
||||
storeSentServerSupports(txn, p.getServerSupports());
|
||||
} catch (FormatException e) {
|
||||
throw new DbException();
|
||||
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mailboxUnpaired(Transaction txn) throws DbException {
|
||||
Map<ContactId, MailboxUpdate> localUpdates = new HashMap<>();
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
MailboxUpdate u = sendUpdateNoMailbox(txn, c);
|
||||
localUpdates.put(c.getId(), u);
|
||||
sendUpdateNoMailbox(txn, c);
|
||||
}
|
||||
txn.attach(new MailboxUnpairedEvent(localUpdates));
|
||||
// Remove the list of server-supported versions
|
||||
try {
|
||||
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
|
||||
GROUP_KEY_SENT_SERVER_SUPPORTS, NULL_VALUE));
|
||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
||||
} catch (FormatException e) {
|
||||
throw new DbException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverSupportedVersionsReceived(Transaction txn,
|
||||
List<MailboxVersion> serverSupports) throws DbException {
|
||||
try {
|
||||
List<MailboxVersion> oldServerSupports =
|
||||
loadSentServerSupports(txn);
|
||||
if (serverSupports.equals(oldServerSupports)) return;
|
||||
storeSentServerSupports(txn, serverSupports);
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
Group contactGroup = getContactGroup(c);
|
||||
LatestUpdate latest =
|
||||
findLatest(txn, contactGroup.getId(), true);
|
||||
// This method should only be called when we have a mailbox,
|
||||
// in which case we should have sent a local update to every
|
||||
// contact
|
||||
if (latest == null) throw new DbException();
|
||||
BdfList body =
|
||||
clientHelper.getMessageAsList(txn, latest.messageId);
|
||||
MailboxUpdate oldUpdate = parseUpdate(body);
|
||||
if (!oldUpdate.hasMailbox()) throw new DbException();
|
||||
MailboxUpdateWithMailbox newUpdate = updateServerSupports(
|
||||
(MailboxUpdateWithMailbox) oldUpdate, serverSupports);
|
||||
storeMessageReplaceLatest(txn, contactGroup.getId(), newUpdate,
|
||||
latest);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException();
|
||||
}
|
||||
}
|
||||
|
||||
private void storeSentServerSupports(Transaction txn,
|
||||
List<MailboxVersion> serverSupports)
|
||||
throws DbException, FormatException {
|
||||
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
|
||||
GROUP_KEY_SENT_SERVER_SUPPORTS,
|
||||
encodeSupportsList(serverSupports)));
|
||||
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
|
||||
}
|
||||
|
||||
private List<MailboxVersion> loadSentServerSupports(Transaction txn)
|
||||
throws DbException, FormatException {
|
||||
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
|
||||
localGroup.getId());
|
||||
BdfList serverSupports =
|
||||
meta.getOptionalList(GROUP_KEY_SENT_SERVER_SUPPORTS);
|
||||
if (serverSupports == null) return emptyList();
|
||||
return clientHelper.parseMailboxVersionList(serverSupports);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link MailboxUpdateWithMailbox} that updates the list
|
||||
* of server-supported API versions in the given
|
||||
* {@link MailboxUpdateWithMailbox}.
|
||||
*/
|
||||
private MailboxUpdateWithMailbox updateServerSupports(
|
||||
MailboxUpdateWithMailbox old, List<MailboxVersion> serverSupports) {
|
||||
MailboxProperties oldProps = old.getMailboxProperties();
|
||||
MailboxProperties newProps = new MailboxProperties(oldProps.getOnion(),
|
||||
oldProps.getAuthToken(), serverSupports,
|
||||
requireNonNull(oldProps.getInboxId()),
|
||||
requireNonNull(oldProps.getOutboxId()));
|
||||
return new MailboxUpdateWithMailbox(old.getClientSupports(), newProps);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,19 +239,19 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||
* supported Mailbox API version(s). All of which the contact needs to
|
||||
* communicate with our Mailbox.
|
||||
*/
|
||||
private MailboxUpdateWithMailbox createAndSendUpdateWithMailbox(
|
||||
Transaction txn, Contact c, List<MailboxVersion> serverSupports,
|
||||
String ownOnion) throws DbException {
|
||||
MailboxProperties properties = new MailboxProperties(ownOnion,
|
||||
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,
|
||||
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
||||
serverSupports,
|
||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
||||
MailboxUpdateWithMailbox u =
|
||||
MailboxUpdate u =
|
||||
new MailboxUpdateWithMailbox(clientSupports, properties);
|
||||
Group g = getContactGroup(c);
|
||||
storeMessageReplaceLatest(txn, g.getId(), u);
|
||||
return u;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,12 +260,11 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||
* Mailbox that they can use. It still includes the list of Mailbox API
|
||||
* version(s) that we support as a client.
|
||||
*/
|
||||
private MailboxUpdate sendUpdateNoMailbox(Transaction txn, Contact c)
|
||||
private void sendUpdateNoMailbox(Transaction txn, Contact c)
|
||||
throws DbException {
|
||||
Group g = getContactGroup(c);
|
||||
MailboxUpdate u = new MailboxUpdate(clientSupports);
|
||||
storeMessageReplaceLatest(txn, g.getId(), u);
|
||||
return u;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -397,27 +289,21 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
||||
MailboxUpdate u) throws DbException {
|
||||
try {
|
||||
LatestUpdate latest = findLatest(txn, g, true);
|
||||
storeMessageReplaceLatest(txn, g, u, latest);
|
||||
long version = latest == null ? 1 : latest.version + 1;
|
||||
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
||||
encodeProperties(version, u));
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_VERSION, version);
|
||||
meta.put(MSG_KEY_LOCAL, true);
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
if (latest != null) {
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
||||
MailboxUpdate u, @Nullable LatestUpdate latest)
|
||||
throws DbException, FormatException {
|
||||
long version = latest == null ? 1 : latest.version + 1;
|
||||
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
||||
encodeProperties(version, u));
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_VERSION, version);
|
||||
meta.put(MSG_KEY_LOCAL, true);
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
if (latest != null) {
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
|
||||
throws DbException, FormatException {
|
||||
|
||||
@@ -1,464 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
|
||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.util.IoUtils.delete;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
|
||||
EventListener {
|
||||
|
||||
/**
|
||||
* When the worker is started it checks for data to send. If data is ready
|
||||
* to send, the worker waits for a connectivity check, then writes and
|
||||
* uploads a file and checks again for data to send.
|
||||
* <p>
|
||||
* If data is due to be sent at some time in the future, the worker
|
||||
* schedules a wakeup for that time and also listens for events indicating
|
||||
* that new data may be ready to send.
|
||||
* <p>
|
||||
* If there's no data to send, the worker listens for events indicating
|
||||
* that new data may be ready to send.
|
||||
* <p>
|
||||
* Whenever we're directly connected to the contact, the worker doesn't
|
||||
* check for data to send or start connectivity checks until the contact
|
||||
* disconnects. However, if the worker has already started writing and
|
||||
* uploading a file when the contact connects, the worker will finish the
|
||||
* upload.
|
||||
*/
|
||||
private enum State {
|
||||
CREATED,
|
||||
CONNECTED_TO_CONTACT,
|
||||
CHECKING_FOR_DATA,
|
||||
WAITING_FOR_DATA,
|
||||
CONNECTIVITY_CHECK,
|
||||
WRITING_UPLOADING,
|
||||
DESTROYED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(MailboxUploadWorker.class.getName());
|
||||
|
||||
/**
|
||||
* When we're waiting for data to send and an event indicates that new data
|
||||
* may have become available, wait this long before checking the DB. This
|
||||
* should help to avoid creating lots of small files when several acks or
|
||||
* messages become available to send in a short period (eg when reading a
|
||||
* file downloaded from a mailbox).
|
||||
* <p>
|
||||
* Package access for testing.
|
||||
*/
|
||||
static final long CHECK_DELAY_MS = 5_000;
|
||||
|
||||
/**
|
||||
* How long to wait before retrying when an exception occurs while writing
|
||||
* a file.
|
||||
* <p>
|
||||
* Package access for testing.
|
||||
*/
|
||||
static final long RETRY_DELAY_MS = MINUTES.toMillis(1);
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final Clock clock;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final EventBus eventBus;
|
||||
private final ConnectionRegistry connectionRegistry;
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxFileManager mailboxFileManager;
|
||||
private final MailboxProperties mailboxProperties;
|
||||
private final MailboxFolderId folderId;
|
||||
private final ContactId contactId;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private State state = State.CREATED;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable wakeupTask = null, checkTask = null, apiCall = null;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private File file = null;
|
||||
|
||||
MailboxUploadWorker(@IoExecutor Executor ioExecutor,
|
||||
DatabaseComponent db,
|
||||
Clock clock,
|
||||
TaskScheduler taskScheduler,
|
||||
EventBus eventBus,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager,
|
||||
MailboxProperties mailboxProperties,
|
||||
MailboxFolderId folderId,
|
||||
ContactId contactId) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.db = db;
|
||||
this.clock = clock;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.eventBus = eventBus;
|
||||
this.connectionRegistry = connectionRegistry;
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxFileManager = mailboxFileManager;
|
||||
this.mailboxProperties = mailboxProperties;
|
||||
this.folderId = folderId;
|
||||
this.contactId = contactId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
synchronized (lock) {
|
||||
// Don't allow the worker to be reused
|
||||
if (state != State.CREATED) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
}
|
||||
ioExecutor.execute(this::checkForDataToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
Cancellable wakeupTask, checkTask, apiCall;
|
||||
File file;
|
||||
synchronized (lock) {
|
||||
state = State.DESTROYED;
|
||||
wakeupTask = this.wakeupTask;
|
||||
this.wakeupTask = null;
|
||||
checkTask = this.checkTask;
|
||||
this.checkTask = null;
|
||||
apiCall = this.apiCall;
|
||||
this.apiCall = null;
|
||||
file = this.file;
|
||||
this.file = null;
|
||||
}
|
||||
if (wakeupTask != null) wakeupTask.cancel();
|
||||
if (checkTask != null) checkTask.cancel();
|
||||
if (apiCall != null) apiCall.cancel();
|
||||
if (file != null) delete(file);
|
||||
connectivityChecker.removeObserver(this);
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void checkForDataToSend() {
|
||||
synchronized (lock) {
|
||||
checkTask = null;
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
// Check whether we're directly connected to the contact. Calling
|
||||
// this while holding the lock isn't ideal, but it avoids races
|
||||
if (connectionRegistry.isConnected(contactId)) {
|
||||
state = State.CONNECTED_TO_CONTACT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.info("Checking for data to send");
|
||||
try {
|
||||
db.transaction(true, txn -> {
|
||||
long nextSendTime;
|
||||
if (db.containsAcksToSend(txn, contactId)) {
|
||||
nextSendTime = 0L;
|
||||
} else {
|
||||
nextSendTime = db.getNextSendTime(txn, contactId,
|
||||
MAX_LATENCY);
|
||||
}
|
||||
// Handle the result on the event executor to avoid races with
|
||||
// incoming events
|
||||
txn.attach(() -> handleNextSendTime(nextSendTime));
|
||||
});
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void handleNextSendTime(long nextSendTime) {
|
||||
if (nextSendTime == Long.MAX_VALUE) {
|
||||
// Nothing is sendable now or due to be sent in the future. Wait
|
||||
// for an event indicating that new data may be ready to send
|
||||
waitForDataToSend();
|
||||
} else {
|
||||
// Work out the delay until data's ready to send (may be negative)
|
||||
long delay = nextSendTime - clock.currentTimeMillis();
|
||||
if (delay > 0) {
|
||||
// Schedule a wakeup when data will be ready to send. If an
|
||||
// event is received in the meantime indicating that new data
|
||||
// may be ready to send, we'll cancel the wakeup
|
||||
scheduleWakeup(delay);
|
||||
} else {
|
||||
// Data is ready to send now
|
||||
checkConnectivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void waitForDataToSend() {
|
||||
synchronized (lock) {
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
state = State.WAITING_FOR_DATA;
|
||||
LOG.info("Waiting for data to send");
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void scheduleWakeup(long delay) {
|
||||
synchronized (lock) {
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
state = State.WAITING_FOR_DATA;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Scheduling wakeup in " + delay + " ms");
|
||||
}
|
||||
wakeupTask = taskScheduler.schedule(this::wakeUp, ioExecutor,
|
||||
delay, MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void wakeUp() {
|
||||
LOG.info("Woke up");
|
||||
synchronized (lock) {
|
||||
wakeupTask = null;
|
||||
if (state != State.WAITING_FOR_DATA) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
}
|
||||
checkForDataToSend();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void checkConnectivity() {
|
||||
synchronized (lock) {
|
||||
if (state != State.CHECKING_FOR_DATA) return;
|
||||
state = State.CONNECTIVITY_CHECK;
|
||||
}
|
||||
LOG.info("Checking connectivity");
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
LOG.info("Connectivity check succeeded");
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTIVITY_CHECK) return;
|
||||
state = State.WRITING_UPLOADING;
|
||||
}
|
||||
ioExecutor.execute(this::writeAndUploadFile);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void writeAndUploadFile() {
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
}
|
||||
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
|
||||
File file;
|
||||
try {
|
||||
file = mailboxFileManager.createAndWriteTempFileForUpload(
|
||||
contactId, sessionRecord);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
// Try again after a delay
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
||||
ioExecutor, RETRY_DELAY_MS, MILLISECONDS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
boolean deleteFile = false;
|
||||
synchronized (lock) {
|
||||
if (state == State.WRITING_UPLOADING) {
|
||||
this.file = file;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(() -> apiCallUploadFile(file,
|
||||
sessionRecord)));
|
||||
} else {
|
||||
deleteFile = true;
|
||||
}
|
||||
}
|
||||
if (deleteFile) delete(file);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallUploadFile(File file,
|
||||
OutgoingSessionRecord sessionRecord)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
}
|
||||
LOG.info("Uploading file");
|
||||
mailboxApi.addFile(mailboxProperties, folderId, file);
|
||||
markMessagesSentOrAcked(sessionRecord);
|
||||
delete(file);
|
||||
synchronized (lock) {
|
||||
if (state != State.WRITING_UPLOADING) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
apiCall = null;
|
||||
this.file = null;
|
||||
}
|
||||
checkForDataToSend();
|
||||
}
|
||||
|
||||
private void markMessagesSentOrAcked(OutgoingSessionRecord sessionRecord) {
|
||||
Collection<MessageId> acked = sessionRecord.getAckedIds();
|
||||
Collection<MessageId> sent = sessionRecord.getSentIds();
|
||||
try {
|
||||
db.transaction(false, txn -> {
|
||||
if (!acked.isEmpty()) {
|
||||
db.setAckSent(txn, contactId, acked);
|
||||
}
|
||||
if (!sent.isEmpty()) {
|
||||
db.setMessagesSent(txn, contactId, sent, MAX_LATENCY);
|
||||
}
|
||||
});
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageToAckEvent) {
|
||||
MessageToAckEvent m = (MessageToAckEvent) e;
|
||||
if (m.getContactId().equals(contactId)) {
|
||||
LOG.info("Message to ack");
|
||||
onDataToSend();
|
||||
}
|
||||
} else if (e instanceof MessageSharedEvent) {
|
||||
MessageSharedEvent m = (MessageSharedEvent) e;
|
||||
// If the contact is present in the map (ie the value is not null)
|
||||
// and the value is true, the message's group is shared with the
|
||||
// contact and therefore the message may now be sendable
|
||||
if (m.getGroupVisibility().get(contactId) == TRUE) {
|
||||
LOG.info("Message shared");
|
||||
onDataToSend();
|
||||
}
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getVisibility() == SHARED &&
|
||||
g.getAffectedContacts().contains(contactId)) {
|
||||
LOG.info("Group shared");
|
||||
onDataToSend();
|
||||
}
|
||||
} else if (e instanceof ContactConnectedEvent) {
|
||||
ContactConnectedEvent c = (ContactConnectedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
LOG.info("Contact connected");
|
||||
onContactConnected();
|
||||
}
|
||||
} else if (e instanceof ContactDisconnectedEvent) {
|
||||
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
LOG.info("Contact disconnected");
|
||||
onContactDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onDataToSend() {
|
||||
Cancellable wakeupTask;
|
||||
synchronized (lock) {
|
||||
if (state != State.WAITING_FOR_DATA) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
wakeupTask = this.wakeupTask;
|
||||
this.wakeupTask = null;
|
||||
// Delay the check to avoid creating lots of small files
|
||||
checkTask = taskScheduler.schedule(this::checkForDataToSend,
|
||||
ioExecutor, CHECK_DELAY_MS, MILLISECONDS);
|
||||
}
|
||||
// If we had scheduled a wakeup when data was due to be sent, cancel it
|
||||
if (wakeupTask != null) wakeupTask.cancel();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactConnected() {
|
||||
Cancellable wakeupTask = null, checkTask = null;
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
// If we're checking for data to send, waiting for data to send,
|
||||
// or checking connectivity then wait until we disconnect from
|
||||
// the contact before proceeding. If we're writing or uploading
|
||||
// a file then continue
|
||||
if (state == State.CHECKING_FOR_DATA ||
|
||||
state == State.WAITING_FOR_DATA ||
|
||||
state == State.CONNECTIVITY_CHECK) {
|
||||
state = State.CONNECTED_TO_CONTACT;
|
||||
wakeupTask = this.wakeupTask;
|
||||
this.wakeupTask = null;
|
||||
checkTask = this.checkTask;
|
||||
this.checkTask = null;
|
||||
}
|
||||
}
|
||||
if (wakeupTask != null) wakeupTask.cancel();
|
||||
if (checkTask != null) checkTask.cancel();
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactDisconnected() {
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTED_TO_CONTACT) return;
|
||||
state = State.CHECKING_FOR_DATA;
|
||||
}
|
||||
ioExecutor.execute(this::checkForDataToSend);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface MailboxWorkerFactory {
|
||||
|
||||
MailboxWorker createUploadWorker(ConnectivityChecker connectivityChecker,
|
||||
MailboxProperties properties, MailboxFolderId folderId,
|
||||
ContactId contactId);
|
||||
|
||||
MailboxWorker createDownloadWorkerForContactMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties);
|
||||
|
||||
MailboxWorker createDownloadWorkerForOwnMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties);
|
||||
|
||||
MailboxWorker createContactListWorkerForOwnMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxProperties properties);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final Clock clock;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final EventBus eventBus;
|
||||
private final ConnectionRegistry connectionRegistry;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxFileManager mailboxFileManager;
|
||||
private final MailboxUpdateManager mailboxUpdateManager;
|
||||
|
||||
@Inject
|
||||
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
|
||||
DatabaseComponent db,
|
||||
Clock clock,
|
||||
TaskScheduler taskScheduler,
|
||||
EventBus eventBus,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager,
|
||||
MailboxUpdateManager mailboxUpdateManager) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.db = db;
|
||||
this.clock = clock;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.eventBus = eventBus;
|
||||
this.connectionRegistry = connectionRegistry;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxFileManager = mailboxFileManager;
|
||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxWorker createUploadWorker(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxProperties properties, MailboxFolderId folderId,
|
||||
ContactId contactId) {
|
||||
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
|
||||
clock, taskScheduler, eventBus, connectionRegistry,
|
||||
connectivityChecker, mailboxApiCaller, mailboxApi,
|
||||
mailboxFileManager, properties, folderId, contactId);
|
||||
eventBus.addListener(worker);
|
||||
return worker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxWorker createDownloadWorkerForContactMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties) {
|
||||
return new ContactMailboxDownloadWorker(connectivityChecker,
|
||||
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||
mailboxFileManager, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxWorker createDownloadWorkerForOwnMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
MailboxProperties properties) {
|
||||
return new OwnMailboxDownloadWorker(connectivityChecker,
|
||||
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||
mailboxFileManager, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxWorker createContactListWorkerForOwnMailbox(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxProperties properties) {
|
||||
OwnMailboxContactListWorker worker = new OwnMailboxContactListWorker(
|
||||
ioExecutor, db, eventBus, connectivityChecker, mailboxApiCaller,
|
||||
mailboxApi, mailboxUpdateManager, properties);
|
||||
eventBus.addListener(worker);
|
||||
return worker;
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class OwnMailboxClient implements MailboxClient, ConnectivityObserver {
|
||||
|
||||
/**
|
||||
* How often to check our own mailbox's connectivity.
|
||||
* <p>
|
||||
* Package access for testing.
|
||||
*/
|
||||
static final long CONNECTIVITY_CHECK_INTERVAL_MS = HOURS.toMillis(1);
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(OwnMailboxClient.class.getName());
|
||||
|
||||
private final MailboxWorkerFactory workerFactory;
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final TorReachabilityMonitor reachabilityMonitor;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final Executor ioExecutor;
|
||||
private final MailboxProperties properties;
|
||||
private final MailboxWorker contactListWorker;
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Upload workers: one worker per contact assigned for upload.
|
||||
*/
|
||||
@GuardedBy("lock")
|
||||
private final Map<ContactId, MailboxWorker> uploadWorkers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Download worker: shared between all contacts assigned for download.
|
||||
* Null if no contacts are assigned for download.
|
||||
*/
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private MailboxWorker downloadWorker = null;
|
||||
|
||||
/**
|
||||
* IDs of contacts assigned for download, so that we know when to
|
||||
* create/destroy the download worker.
|
||||
*/
|
||||
@GuardedBy("lock")
|
||||
private final Set<ContactId> assignedForDownload = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Scheduled task for periodically checking whether the mailbox is
|
||||
* reachable.
|
||||
*/
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable connectivityTask = null;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private boolean destroyed = false;
|
||||
|
||||
OwnMailboxClient(MailboxWorkerFactory workerFactory,
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor reachabilityMonitor,
|
||||
TaskScheduler taskScheduler,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
MailboxProperties properties) {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
this.workerFactory = workerFactory;
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.reachabilityMonitor = reachabilityMonitor;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.properties = properties;
|
||||
contactListWorker = workerFactory.createContactListWorkerForOwnMailbox(
|
||||
connectivityChecker, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
checkConnectivity();
|
||||
contactListWorker.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
List<MailboxWorker> uploadWorkers;
|
||||
MailboxWorker downloadWorker;
|
||||
Cancellable connectivityTask;
|
||||
synchronized (lock) {
|
||||
uploadWorkers = new ArrayList<>(this.uploadWorkers.values());
|
||||
this.uploadWorkers.clear();
|
||||
downloadWorker = this.downloadWorker;
|
||||
this.downloadWorker = null;
|
||||
connectivityTask = this.connectivityTask;
|
||||
this.connectivityTask = null;
|
||||
destroyed = true;
|
||||
}
|
||||
// Destroy the workers (with apologies to Mr Marx and Mr Engels)
|
||||
for (MailboxWorker worker : uploadWorkers) worker.destroy();
|
||||
if (downloadWorker != null) downloadWorker.destroy();
|
||||
// If a connectivity check is scheduled, cancel it
|
||||
if (connectivityTask != null) connectivityTask.cancel();
|
||||
contactListWorker.destroy();
|
||||
// The connectivity checker belongs to the client, so it should be
|
||||
// destroyed. The Tor reachability monitor is shared between clients,
|
||||
// so it should not be destroyed
|
||||
connectivityChecker.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignContactForUpload(ContactId contactId,
|
||||
MailboxProperties properties, MailboxFolderId folderId) {
|
||||
LOG.info("Contact assigned for upload");
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
|
||||
connectivityChecker, properties, folderId, contactId);
|
||||
synchronized (lock) {
|
||||
MailboxWorker old = uploadWorkers.put(contactId, uploadWorker);
|
||||
if (old != null) throw new IllegalStateException();
|
||||
}
|
||||
uploadWorker.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deassignContactForUpload(ContactId contactId) {
|
||||
LOG.info("Contact deassigned for upload");
|
||||
MailboxWorker uploadWorker;
|
||||
synchronized (lock) {
|
||||
uploadWorker = uploadWorkers.remove(contactId);
|
||||
}
|
||||
if (uploadWorker != null) uploadWorker.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignContactForDownload(ContactId contactId,
|
||||
MailboxProperties properties, MailboxFolderId folderId) {
|
||||
LOG.info("Contact assigned for download");
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
// Create a download worker if we don't already have one. The worker
|
||||
// will use the API to discover which folders have files to download,
|
||||
// so it doesn't need to track the set of assigned contacts
|
||||
MailboxWorker toStart = null;
|
||||
synchronized (lock) {
|
||||
if (!assignedForDownload.add(contactId)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (downloadWorker == null) {
|
||||
toStart = workerFactory.createDownloadWorkerForOwnMailbox(
|
||||
connectivityChecker, reachabilityMonitor, properties);
|
||||
downloadWorker = toStart;
|
||||
}
|
||||
}
|
||||
if (toStart != null) toStart.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deassignContactForDownload(ContactId contactId) {
|
||||
LOG.info("Contact deassigned for download");
|
||||
// If there are no more contacts assigned for download, destroy the
|
||||
// download worker
|
||||
MailboxWorker toDestroy = null;
|
||||
synchronized (lock) {
|
||||
if (!assignedForDownload.remove(contactId)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (assignedForDownload.isEmpty()) {
|
||||
toDestroy = downloadWorker;
|
||||
downloadWorker = null;
|
||||
}
|
||||
}
|
||||
if (toDestroy != null) toDestroy.destroy();
|
||||
}
|
||||
|
||||
private void checkConnectivity() {
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
connectivityTask = null;
|
||||
}
|
||||
connectivityChecker.checkConnectivity(properties, this);
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
boolean removeObserver;
|
||||
synchronized (lock) {
|
||||
removeObserver = destroyed;
|
||||
}
|
||||
if (removeObserver) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
connectivityTask = taskScheduler.schedule(this::checkConnectivity,
|
||||
ioExecutor, CONNECTIVITY_CHECK_INTERVAL_MS, MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,14 @@ 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;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
@@ -31,7 +28,6 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
private final TransactionManager db;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
|
||||
@Inject
|
||||
OwnMailboxConnectivityChecker(Clock clock,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
@@ -59,13 +55,11 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
private boolean checkConnectivityAndStoreResult(
|
||||
MailboxProperties properties) throws DbException {
|
||||
try {
|
||||
LOG.info("Checking whether own mailbox is reachable");
|
||||
List<MailboxVersion> serverSupports =
|
||||
mailboxApi.getServerSupports(properties);
|
||||
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
|
||||
LOG.info("Own mailbox is reachable");
|
||||
long now = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> mailboxSettingsManager
|
||||
.recordSuccessfulConnection(txn, now, serverSupports));
|
||||
.recordSuccessfulConnection(txn, now));
|
||||
// Call the observers and cache the result
|
||||
onConnectivityCheckSucceeded(now);
|
||||
return false; // Don't retry
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class OwnMailboxContactListWorker
|
||||
implements MailboxWorker, ConnectivityObserver, EventListener {
|
||||
|
||||
/**
|
||||
* When the worker is started it waits for a connectivity check, then
|
||||
* fetches the remote contact list and compares it to the local contact
|
||||
* list.
|
||||
* <p>
|
||||
* Any contacts that are missing from the remote list are added to the
|
||||
* mailbox's contact list, while any contacts that are missing from the
|
||||
* local list are removed from the mailbox's contact list.
|
||||
* <p>
|
||||
* Once the remote contact list has been brought up to date, the worker
|
||||
* waits for events indicating that contacts have been added or removed.
|
||||
* Each time an event is received, the worker updates the mailbox's
|
||||
* contact list and then goes back to waiting.
|
||||
*/
|
||||
private enum State {
|
||||
CREATED,
|
||||
CONNECTIVITY_CHECK,
|
||||
FETCHING_CONTACT_LIST,
|
||||
UPDATING_CONTACT_LIST,
|
||||
WAITING_FOR_CHANGES,
|
||||
DESTROYED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(OwnMailboxContactListWorker.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final EventBus eventBus;
|
||||
private final ConnectivityChecker connectivityChecker;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
private final MailboxApi mailboxApi;
|
||||
private final MailboxUpdateManager mailboxUpdateManager;
|
||||
private final MailboxProperties mailboxProperties;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private State state = State.CREATED;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable apiCall = null;
|
||||
|
||||
/**
|
||||
* A queue of updates waiting to be applied to the remote contact list.
|
||||
*/
|
||||
@GuardedBy("lock")
|
||||
private final Queue<Update> updates = new LinkedList<>();
|
||||
|
||||
OwnMailboxContactListWorker(@IoExecutor Executor ioExecutor,
|
||||
DatabaseComponent db,
|
||||
EventBus eventBus,
|
||||
ConnectivityChecker connectivityChecker,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxUpdateManager mailboxUpdateManager,
|
||||
MailboxProperties mailboxProperties) {
|
||||
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.db = db;
|
||||
this.connectivityChecker = connectivityChecker;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.mailboxUpdateManager = mailboxUpdateManager;
|
||||
this.mailboxProperties = mailboxProperties;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
LOG.info("Started");
|
||||
synchronized (lock) {
|
||||
if (state != State.CREATED) return;
|
||||
state = State.CONNECTIVITY_CHECK;
|
||||
}
|
||||
// Avoid leaking observer in case destroy() is called concurrently
|
||||
// before observer is added
|
||||
connectivityChecker.checkConnectivity(mailboxProperties, this);
|
||||
boolean destroyed;
|
||||
synchronized (lock) {
|
||||
destroyed = state == State.DESTROYED;
|
||||
}
|
||||
if (destroyed) connectivityChecker.removeObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
LOG.info("Destroyed");
|
||||
Cancellable apiCall;
|
||||
synchronized (lock) {
|
||||
state = State.DESTROYED;
|
||||
apiCall = this.apiCall;
|
||||
this.apiCall = null;
|
||||
}
|
||||
if (apiCall != null) apiCall.cancel();
|
||||
connectivityChecker.removeObserver(this);
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectivityCheckSucceeded() {
|
||||
LOG.info("Connectivity check succeeded");
|
||||
synchronized (lock) {
|
||||
if (state != State.CONNECTIVITY_CHECK) return;
|
||||
state = State.FETCHING_CONTACT_LIST;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
||||
new SimpleApiCall(this::apiCallFetchContactList));
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallFetchContactList() throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Fetching remote contact list");
|
||||
Collection<ContactId> remote =
|
||||
mailboxApi.getContacts(mailboxProperties);
|
||||
ioExecutor.execute(() -> loadLocalContactList(remote));
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void loadLocalContactList(Collection<ContactId> remote) {
|
||||
synchronized (lock) {
|
||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||
apiCall = null;
|
||||
}
|
||||
LOG.info("Loading local contact list");
|
||||
try {
|
||||
db.transaction(true, txn -> {
|
||||
Collection<Contact> local = db.getContacts(txn);
|
||||
// Handle the result on the event executor to avoid races with
|
||||
// incoming events
|
||||
txn.attach(() -> reconcileContactLists(local, remote));
|
||||
});
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void reconcileContactLists(Collection<Contact> local,
|
||||
Collection<ContactId> remote) {
|
||||
Set<ContactId> localIds = new HashSet<>();
|
||||
for (Contact c : local) localIds.add(c.getId());
|
||||
remote = new HashSet<>(remote);
|
||||
synchronized (lock) {
|
||||
if (state != State.FETCHING_CONTACT_LIST) return;
|
||||
for (ContactId c : localIds) {
|
||||
if (!remote.contains(c)) updates.add(new Update(true, c));
|
||||
}
|
||||
for (ContactId c : remote) {
|
||||
if (!localIds.contains(c)) updates.add(new Update(false, c));
|
||||
}
|
||||
if (updates.isEmpty()) {
|
||||
LOG.info("Contact list is up to date");
|
||||
state = State.WAITING_FOR_CHANGES;
|
||||
} else {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(updates.size() + " updates to apply");
|
||||
}
|
||||
state = State.UPDATING_CONTACT_LIST;
|
||||
ioExecutor.execute(this::updateContactList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void updateContactList() {
|
||||
Update update;
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
update = updates.poll();
|
||||
if (update == null) {
|
||||
LOG.info("No more updates to process");
|
||||
state = State.WAITING_FOR_CHANGES;
|
||||
apiCall = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (update.add) loadMailboxProperties(update.contactId);
|
||||
else removeContact(update.contactId);
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void loadMailboxProperties(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Loading mailbox properties for contact");
|
||||
try {
|
||||
MailboxUpdate mailboxUpdate = db.transactionWithResult(true, txn ->
|
||||
mailboxUpdateManager.getLocalUpdate(txn, c));
|
||||
if (mailboxUpdate instanceof MailboxUpdateWithMailbox) {
|
||||
addContact(c, (MailboxUpdateWithMailbox) mailboxUpdate);
|
||||
} else {
|
||||
// Our own mailbox was concurrently unpaired. This worker will
|
||||
// be destroyed soon, so we can stop here
|
||||
LOG.info("Own mailbox was unpaired");
|
||||
}
|
||||
} catch (NoSuchContactException e) {
|
||||
// Contact was removed concurrently. Move on to the next update.
|
||||
// Later we may process a removal update for this contact, which
|
||||
// was never added to the mailbox's contact list. The removal API
|
||||
// call should fail safely with a TolerableFailureException
|
||||
LOG.info("No such contact");
|
||||
updateContactList();
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void addContact(ContactId c, MailboxUpdateWithMailbox withMailbox) {
|
||||
MailboxProperties props = withMailbox.getMailboxProperties();
|
||||
MailboxContact contact = new MailboxContact(c, props.getAuthToken(),
|
||||
requireNonNull(props.getInboxId()),
|
||||
requireNonNull(props.getOutboxId()));
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||
apiCallAddContact(contact)));
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallAddContact(MailboxContact contact)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Adding contact to remote contact list");
|
||||
mailboxApi.addContact(mailboxProperties, contact);
|
||||
updateContactList();
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void removeContact(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||
apiCallRemoveContact(c)));
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void apiCallRemoveContact(ContactId c)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST) return;
|
||||
}
|
||||
LOG.info("Removing contact from remote contact list");
|
||||
try {
|
||||
mailboxApi.deleteContact(mailboxProperties, c);
|
||||
} catch (TolerableFailureException e) {
|
||||
// Catch this so we can continue to the next update
|
||||
LOG.warning("Contact does not exist");
|
||||
}
|
||||
updateContactList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactAddedEvent) {
|
||||
LOG.info("Contact added");
|
||||
onContactAdded(((ContactAddedEvent) e).getContactId());
|
||||
} else if (e instanceof ContactRemovedEvent) {
|
||||
LOG.info("Contact removed");
|
||||
onContactRemoved(((ContactRemovedEvent) e).getContactId());
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactAdded(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST &&
|
||||
state != State.WAITING_FOR_CHANGES) {
|
||||
return;
|
||||
}
|
||||
updates.add(new Update(true, c));
|
||||
if (state == State.WAITING_FOR_CHANGES) {
|
||||
state = State.UPDATING_CONTACT_LIST;
|
||||
ioExecutor.execute(this::updateContactList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventExecutor
|
||||
private void onContactRemoved(ContactId c) {
|
||||
synchronized (lock) {
|
||||
if (state != State.UPDATING_CONTACT_LIST &&
|
||||
state != State.WAITING_FOR_CHANGES) {
|
||||
return;
|
||||
}
|
||||
updates.add(new Update(false, c));
|
||||
if (state == State.WAITING_FOR_CHANGES) {
|
||||
state = State.UPDATING_CONTACT_LIST;
|
||||
ioExecutor.execute(this::updateContactList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An update that should be applied to the remote contact list.
|
||||
*/
|
||||
private static class Update {
|
||||
|
||||
/**
|
||||
* True if the contact should be added, false if the contact should be
|
||||
* removed.
|
||||
*/
|
||||
private final boolean add;
|
||||
private final ContactId contactId;
|
||||
|
||||
private Update(boolean add, ContactId contactId) {
|
||||
this.add = add;
|
||||
this.contactId = contactId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.Collections.shuffle;
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class OwnMailboxDownloadWorker extends MailboxDownloadWorker {
|
||||
|
||||
/**
|
||||
* The maximum number of files that will be downloaded before checking
|
||||
* again for folders with available files. This ensures that if a file
|
||||
* arrives during a download cycle, its folder will be checked within a
|
||||
* reasonable amount of time even if some other folder has a very large
|
||||
* number of files.
|
||||
* <p>
|
||||
* Package access for testing.
|
||||
*/
|
||||
static final int MAX_ROUND_ROBIN_FILES = 1000;
|
||||
|
||||
OwnMailboxDownloadWorker(
|
||||
ConnectivityChecker connectivityChecker,
|
||||
TorReachabilityMonitor torReachabilityMonitor,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
MailboxFileManager mailboxFileManager,
|
||||
MailboxProperties mailboxProperties) {
|
||||
super(connectivityChecker, torReachabilityMonitor, mailboxApiCaller,
|
||||
mailboxApi, mailboxFileManager, mailboxProperties);
|
||||
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApiCall createApiCallForDownloadCycle() {
|
||||
return new SimpleApiCall(this::apiCallListFolders);
|
||||
}
|
||||
|
||||
private void apiCallListFolders() throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
LOG.info("Listing folders with available files");
|
||||
List<MailboxFolderId> folders =
|
||||
mailboxApi.getFolders(mailboxProperties);
|
||||
if (folders.isEmpty()) onDownloadCycleFinished();
|
||||
else listNextFolder(new LinkedList<>(folders), new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the next folder from `queue` and starts a task to list the
|
||||
* files in the folder and add them to `available`.
|
||||
*/
|
||||
private void listNextFolder(Queue<MailboxFolderId> queue,
|
||||
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
MailboxFolderId folder = queue.remove();
|
||||
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
|
||||
apiCallListFolder(folder, queue, available)));
|
||||
}
|
||||
}
|
||||
|
||||
private void apiCallListFolder(MailboxFolderId folder,
|
||||
Queue<MailboxFolderId> queue,
|
||||
Map<MailboxFolderId, Queue<MailboxFile>> available)
|
||||
throws IOException, ApiException {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
LOG.info("Listing folder");
|
||||
try {
|
||||
List<MailboxFile> files =
|
||||
mailboxApi.getFiles(mailboxProperties, folder);
|
||||
if (!files.isEmpty()) {
|
||||
available.put(folder, new LinkedList<>(files));
|
||||
}
|
||||
} catch (TolerableFailureException e) {
|
||||
LOG.warning("Folder does not exist");
|
||||
}
|
||||
if (queue.isEmpty()) {
|
||||
LOG.info("Finished listing folders");
|
||||
if (available.isEmpty()) onDownloadCycleFinished();
|
||||
else createDownloadQueue(available);
|
||||
} else {
|
||||
listNextFolder(queue, available);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the given folders in round-robin order to create a queue of up to
|
||||
* {@link #MAX_ROUND_ROBIN_FILES} to download.
|
||||
*/
|
||||
private void createDownloadQueue(
|
||||
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
||||
synchronized (lock) {
|
||||
if (state == State.DESTROYED) return;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(available.size() + " folders have available files");
|
||||
}
|
||||
Queue<FolderFile> queue = createRoundRobinQueue(available);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Downloading " + queue.size() + " files");
|
||||
}
|
||||
downloadNextFile(queue);
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
Queue<FolderFile> createRoundRobinQueue(
|
||||
Map<MailboxFolderId, Queue<MailboxFile>> available) {
|
||||
List<MailboxFolderId> roundRobin = new ArrayList<>(available.keySet());
|
||||
// Shuffle the folders so we don't always favour the same folders
|
||||
shuffle(roundRobin);
|
||||
Queue<FolderFile> queue = new LinkedList<>();
|
||||
while (queue.size() < MAX_ROUND_ROBIN_FILES && !available.isEmpty()) {
|
||||
Iterator<MailboxFolderId> it = roundRobin.iterator();
|
||||
while (queue.size() < MAX_ROUND_ROBIN_FILES && it.hasNext()) {
|
||||
MailboxFolderId folder = it.next();
|
||||
Queue<MailboxFile> files = available.get(folder);
|
||||
MailboxFile file = files.remove();
|
||||
queue.add(new FolderFile(folder, file.name));
|
||||
if (files.isEmpty()) {
|
||||
available.remove(folder);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* An interface for converting an onion address to an HTTP URL, allowing the
|
||||
* conversion to be customised for integration tests.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
interface UrlConverter {
|
||||
|
||||
/**
|
||||
* Converts a raw onion address, excluding the .onion suffix, into an
|
||||
* HTTP URL.
|
||||
*/
|
||||
String convertOnionToBaseUrl(String onion);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@NotNullByDefault
|
||||
class UrlConverterImpl implements UrlConverter {
|
||||
|
||||
@Inject
|
||||
UrlConverterImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertOnionToBaseUrl(String onion) {
|
||||
return "http://" + onion + ".onion";
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
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;
|
||||
|
||||
@@ -11,8 +13,9 @@ import javax.annotation.concurrent.Immutable;
|
||||
class BackoffFactoryImpl implements BackoffFactory {
|
||||
|
||||
@Override
|
||||
public Backoff createBackoff(int minInterval, int maxInterval,
|
||||
double base) {
|
||||
return new BackoffImpl(minInterval, maxInterval, base);
|
||||
public Backoff createBackoff(EventBus eventBus, TransportId transportId,
|
||||
int minInterval, int maxInterval, double base) {
|
||||
return new BackoffImpl(eventBus, transportId, minInterval, maxInterval,
|
||||
base);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
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;
|
||||
|
||||
@@ -11,11 +14,16 @@ 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(int minInterval, int maxInterval, double base) {
|
||||
BackoffImpl(EventBus eventBus, TransportId transportId,
|
||||
int minInterval, int maxInterval, double base) {
|
||||
this.eventBus = eventBus;
|
||||
this.transportId = transportId;
|
||||
this.minInterval = minInterval;
|
||||
this.maxInterval = maxInterval;
|
||||
this.base = base;
|
||||
@@ -37,6 +45,9 @@ class BackoffImpl implements Backoff {
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
backoff.set(0);
|
||||
int old = backoff.getAndSet(0);
|
||||
if (old > 0) {
|
||||
eventBus.broadcast(new PollingIntervalDecreasedEvent(transportId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.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;
|
||||
@@ -362,6 +363,11 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pollingIntervalDecreased() {
|
||||
eventBus.broadcast(new PollingIntervalDecreasedEvent(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleConnection(DuplexTransportConnection d) {
|
||||
connectionManager.manageIncomingConnection(id, d);
|
||||
|
||||
@@ -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.ConnectionOpenedEvent;
|
||||
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.simplex.SimplexPlugin;
|
||||
@@ -107,10 +107,14 @@ class PollerImpl implements Poller, EventListener {
|
||||
if (!c.isIncoming() && c.isException()) {
|
||||
connectToContact(c.getContactId(), c.getTransportId());
|
||||
}
|
||||
} else if (e instanceof ConnectionOpenedEvent) {
|
||||
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
|
||||
// Reschedule polling, the polling interval may have decreased
|
||||
reschedule(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 TransportActiveEvent) {
|
||||
TransportActiveEvent t = (TransportActiveEvent) e;
|
||||
// Poll the newly activated transport
|
||||
@@ -228,7 +232,7 @@ class PollerImpl implements Poller, EventListener {
|
||||
if (!connected.contains(c))
|
||||
properties.add(new Pair<>(e.getValue(), new Handler(c, t)));
|
||||
}
|
||||
if (!properties.isEmpty()) p.poll(properties);
|
||||
p.poll(properties);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
||||
@Override
|
||||
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
|
||||
properties) {
|
||||
if (getState() != ACTIVE) return;
|
||||
if (properties.isEmpty() || getState() != ACTIVE) return;
|
||||
backoff.increment();
|
||||
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
|
||||
connect(p.getFirst(), p.getSecond());
|
||||
@@ -427,13 +427,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
String address = getBluetoothAddress();
|
||||
if (address != null) {
|
||||
try {
|
||||
descriptor.add(macToBytes(address));
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
if (address != null) descriptor.add(macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@ 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() {
|
||||
}
|
||||
|
||||
@@ -106,8 +106,7 @@ class RemovableDriveManagerImpl
|
||||
@Override
|
||||
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
|
||||
return db.transactionWithResult(true, txn ->
|
||||
db.containsAcksToSend(txn, c) ||
|
||||
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
|
||||
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -285,7 +285,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
if (ip.length == 16) addrs.add(InetAddress.getByAddress(ip));
|
||||
}
|
||||
return addrs;
|
||||
} catch (FormatException | UnknownHostException e) {
|
||||
} catch (IllegalArgumentException | UnknownHostException e) {
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
|
||||
MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, wakefulIoExecutor,
|
||||
backoff, callback, MAX_LATENCY, MAX_IDLE_TIME,
|
||||
CONNECTION_TIMEOUT);
|
||||
|
||||
@@ -246,7 +246,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
|
||||
@Override
|
||||
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
|
||||
properties) {
|
||||
if (getState() != ACTIVE) return;
|
||||
if (properties.isEmpty() || getState() != ACTIVE) return;
|
||||
backoff.increment();
|
||||
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
|
||||
connect(p.getFirst(), p.getSecond());
|
||||
|
||||
@@ -60,8 +60,8 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(PluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
Backoff backoff = backoffFactory.createBackoff(eventBus, ID,
|
||||
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,
|
||||
|
||||
@@ -12,8 +12,7 @@ public interface CircumventionProvider {
|
||||
DEFAULT_OBFS4,
|
||||
NON_DEFAULT_OBFS4,
|
||||
VANILLA,
|
||||
MEEK,
|
||||
SNOWFLAKE
|
||||
MEEK
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,14 +41,13 @@ public interface CircumventionProvider {
|
||||
* Countries where non-default obfs4 or vanilla bridges are likely to work.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] NON_DEFAULT_BRIDGES = {"BY", "RU"};
|
||||
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
||||
|
||||
/**
|
||||
* Countries where vanilla bridges are blocked via DPI but non-default
|
||||
* obfs4 bridges, meek and snowflake may work. Should be a subset of
|
||||
* {@link #BRIDGES}.
|
||||
* obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] DPI_BRIDGES = {"CN", "IR", "TM"};
|
||||
String[] DPI_BRIDGES = {"CN", "IR"};
|
||||
|
||||
/**
|
||||
* Returns true if vanilla Tor connections are blocked in the given country.
|
||||
@@ -70,6 +68,6 @@ public interface CircumventionProvider {
|
||||
List<BridgeType> getSuitableBridgeTypes(String countryCode);
|
||||
|
||||
@IoExecutor
|
||||
List<String> getBridges(BridgeType type, String countryCode,
|
||||
boolean letsEncrypt);
|
||||
List<String> getBridges(BridgeType type);
|
||||
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
@@ -20,7 +18,6 @@ import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
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.CircumventionProvider.BridgeType.SNOWFLAKE;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
|
||||
@Immutable
|
||||
@@ -28,8 +25,6 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeTy
|
||||
class CircumventionProviderImpl implements CircumventionProvider {
|
||||
|
||||
private final static String BRIDGE_FILE_NAME = "bridges";
|
||||
private final static String SNOWFLAKE_PARAMS_FILE_NAME = "snowflake-params";
|
||||
private final static String DEFAULT_COUNTRY_CODE = "ZZ";
|
||||
|
||||
private static final Set<String> BLOCKED_IN_COUNTRIES =
|
||||
new HashSet<>(asList(BLOCKED));
|
||||
@@ -63,7 +58,7 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
||||
} else if (DPI_COUNTRIES.contains(countryCode)) {
|
||||
return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE);
|
||||
return asList(NON_DEFAULT_OBFS4, MEEK);
|
||||
} else {
|
||||
return asList(DEFAULT_OBFS4, VANILLA);
|
||||
}
|
||||
@@ -71,8 +66,7 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
|
||||
@Override
|
||||
@IoExecutor
|
||||
public List<String> getBridges(BridgeType type, String countryCode,
|
||||
boolean letsEncrypt) {
|
||||
public List<String> getBridges(BridgeType type) {
|
||||
InputStream is = requireNonNull(getClass().getClassLoader()
|
||||
.getResourceAsStream(BRIDGE_FILE_NAME));
|
||||
Scanner scanner = new Scanner(is);
|
||||
@@ -85,45 +79,10 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
(type == VANILLA && line.startsWith("v ")) ||
|
||||
(type == MEEK && line.startsWith("m "))) {
|
||||
bridges.add(line.substring(2));
|
||||
} else if (type == SNOWFLAKE && line.startsWith("s ")) {
|
||||
String params = getSnowflakeParams(countryCode, letsEncrypt);
|
||||
bridges.add(line.substring(2) + " " + params);
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
return bridges;
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
String getSnowflakeParams(String countryCode, boolean letsEncrypt) {
|
||||
Map<String, String> params = loadSnowflakeParams();
|
||||
if (countryCode.isEmpty()) countryCode = DEFAULT_COUNTRY_CODE;
|
||||
// If we have parameters for this country code, return them
|
||||
String value = params.get(makeKey(countryCode, letsEncrypt));
|
||||
if (value != null) return value;
|
||||
// Return the default parameters
|
||||
value = params.get(makeKey(DEFAULT_COUNTRY_CODE, letsEncrypt));
|
||||
return requireNonNull(value);
|
||||
}
|
||||
|
||||
private Map<String, String> loadSnowflakeParams() {
|
||||
InputStream is = requireNonNull(getClass().getClassLoader()
|
||||
.getResourceAsStream(SNOWFLAKE_PARAMS_FILE_NAME));
|
||||
Scanner scanner = new Scanner(is);
|
||||
Map<String, String> params = new TreeMap<>();
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
if (line.length() < 5) continue;
|
||||
String key = line.substring(0, 4); // Country code, space, digit
|
||||
String value = line.substring(5);
|
||||
params.put(key, value);
|
||||
}
|
||||
scanner.close();
|
||||
return params;
|
||||
}
|
||||
|
||||
private String makeKey(String countryCode, boolean letsEncrypt) {
|
||||
return countryCode + " " + (letsEncrypt ? "1" : "0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.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;
|
||||
@@ -26,6 +25,7 @@ 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,8 +94,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_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.SNOWFLAKE;
|
||||
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;
|
||||
@@ -124,6 +125,45 @@ 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;
|
||||
@@ -132,7 +172,6 @@ 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;
|
||||
@@ -152,6 +191,7 @@ 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();
|
||||
|
||||
@@ -166,7 +206,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto,
|
||||
PluginCallback callback,
|
||||
String architecture,
|
||||
@@ -184,7 +223,6 @@ 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;
|
||||
@@ -213,10 +251,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return new File(torDirectory, "obfs4proxy");
|
||||
}
|
||||
|
||||
protected File getSnowflakeExecutableFile() {
|
||||
return new File(torDirectory, "snowflake");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return TorConstants.ID;
|
||||
@@ -243,14 +277,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
// Load the settings
|
||||
settings = callback.getSettings();
|
||||
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);
|
||||
}
|
||||
// Install or update the assets if necessary
|
||||
if (!assetsAreUpToDate()) installAssets();
|
||||
if (cookieFile.exists() && !cookieFile.delete())
|
||||
LOG.warning("Old auth cookie not deleted");
|
||||
// Start a new Tor process
|
||||
@@ -312,7 +340,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.setCircuitBuilt(true);
|
||||
state.getAndSetCircuitBuilt(true);
|
||||
}
|
||||
} catch (TorNotRunningException e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -331,21 +359,25 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return doneFile.lastModified() > getLastUpdateTime();
|
||||
}
|
||||
|
||||
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();
|
||||
installSnowflakeExecutable();
|
||||
if (!doneFile.createNewFile())
|
||||
LOG.warning("Failed to create done file");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected void extract(InputStream in, File dest) throws IOException {
|
||||
@@ -369,29 +401,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (!obfs4File.setExecutable(true, true)) throw new IOException();
|
||||
}
|
||||
|
||||
protected void installSnowflakeExecutable() throws IOException {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Installing snowflake binary for " + architecture);
|
||||
File snowflakeFile = getSnowflakeExecutableFile();
|
||||
extract(getSnowflakeInputStream(), snowflakeFile);
|
||||
if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
|
||||
}
|
||||
|
||||
private InputStream getTorInputStream() throws IOException {
|
||||
return getZipInputStream("tor");
|
||||
InputStream in = resourceProvider
|
||||
.getResourceInputStream("tor_" + architecture, ".zip");
|
||||
ZipInputStream zin = new ZipInputStream(in);
|
||||
if (zin.getNextEntry() == null) throw new IOException();
|
||||
return zin;
|
||||
}
|
||||
|
||||
private InputStream getObfs4InputStream() throws IOException {
|
||||
return getZipInputStream("obfs4proxy");
|
||||
}
|
||||
|
||||
private InputStream getSnowflakeInputStream() throws IOException {
|
||||
return getZipInputStream("snowflake");
|
||||
}
|
||||
|
||||
private InputStream getZipInputStream(String basename) throws IOException {
|
||||
InputStream in = resourceProvider
|
||||
.getResourceInputStream(basename + "_" + architecture, ".zip");
|
||||
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
|
||||
ZipInputStream zin = new ZipInputStream(in);
|
||||
if (zin.getNextEntry() == null) throw new IOException();
|
||||
return zin;
|
||||
@@ -416,12 +436,6 @@ 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);
|
||||
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
|
||||
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
|
||||
//noinspection CharsetObjectCanBeUsed
|
||||
return new ByteArrayInputStream(
|
||||
strb.toString().getBytes(Charset.forName("UTF-8")));
|
||||
@@ -498,7 +512,6 @@ 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);
|
||||
});
|
||||
@@ -537,6 +550,7 @@ 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));
|
||||
}
|
||||
@@ -565,13 +579,12 @@ 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 {
|
||||
if (!state.enableNetwork(enable)) return; // Unchanged
|
||||
state.enableNetwork(enable);
|
||||
try {
|
||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||
} catch (TorNotRunningException e) {
|
||||
@@ -579,37 +592,34 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
|
||||
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
|
||||
throws IOException {
|
||||
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
|
||||
try {
|
||||
if (bridgeTypes.isEmpty()) {
|
||||
controlConnection.setConf("UseBridges", "0");
|
||||
controlConnection.resetConf(singletonList("Bridge"));
|
||||
} else {
|
||||
if (enable) {
|
||||
Collection<String> conf = new ArrayList<>();
|
||||
conf.add("UseBridges 1");
|
||||
boolean letsEncrypt = canVerifyLetsEncryptCerts();
|
||||
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, countryCode, letsEncrypt));
|
||||
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
||||
}
|
||||
controlConnection.setConf(conf);
|
||||
} else {
|
||||
controlConnection.setConf("UseBridges", "0");
|
||||
}
|
||||
} catch (TorNotRunningException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this device can verify Let's Encrypt certificates signed
|
||||
* with the IdentTrust DST Root X3 certificate, which expired at the end of
|
||||
* September 2021.
|
||||
*/
|
||||
protected boolean canVerifyLetsEncryptCerts() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
ServerSocket ss = state.setStopped();
|
||||
@@ -617,6 +627,7 @@ 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) {
|
||||
@@ -644,14 +655,58 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
if (state.isNetworkStable()) {
|
||||
LOG.info("Using stable polling interval");
|
||||
return POLLING_INTERVAL_STABLE;
|
||||
} else {
|
||||
LOG.info("Using unstable polling interval");
|
||||
return POLLING_INTERVAL_UNSTABLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
|
||||
properties) {
|
||||
if (getState() != ACTIVE) return;
|
||||
backoff.increment();
|
||||
String ownOnion = this.ownOnion;
|
||||
if (ownOnion == null) {
|
||||
// Our own hidden service hasn't been created yet
|
||||
pollContacts(properties);
|
||||
} else {
|
||||
// If the network is unstable, poll our contacts
|
||||
boolean stable = state.isNetworkStable();
|
||||
if (!stable) pollContacts(properties);
|
||||
// Poll our own hidden service to check if the network is stable
|
||||
wakefulIoExecutor.execute(() -> {
|
||||
LOG.info("Connecting to own hidden service");
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ONION_V3, ownOnion);
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d == null) {
|
||||
LOG.info("Could not connect to own hidden service");
|
||||
state.onStabilityCheckFailed();
|
||||
// If the network was previously considered stable then
|
||||
// we didn't poll our contacts above, so poll them now
|
||||
if (stable) pollContacts(properties);
|
||||
} else {
|
||||
LOG.info("Connected to own hidden service");
|
||||
// Close the connection (this will cause the other end of
|
||||
// the connection to log an EOFException)
|
||||
try {
|
||||
d.getWriter().dispose(false);
|
||||
d.getReader().dispose(false, false);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
state.onStabilityCheckSucceeded();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void pollContacts(
|
||||
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
|
||||
if (properties.isEmpty() || getState() != ACTIVE) return;
|
||||
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
|
||||
connect(p.getFirst(), p.getSecond());
|
||||
}
|
||||
@@ -660,10 +715,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private void connect(TransportProperties p, ConnectionHandler h) {
|
||||
wakefulIoExecutor.execute(() -> {
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
h.handleConnection(d);
|
||||
}
|
||||
if (d != null) h.handleConnection(d);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -784,9 +836,8 @@ 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.setCircuitBuilt(true)) {
|
||||
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,6 +864,9 @@ 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
|
||||
@@ -831,13 +885,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void controlConnectionClosed() {
|
||||
if (state.isTorRunning()) {
|
||||
throw new RuntimeException("Control connection closed");
|
||||
}
|
||||
}
|
||||
|
||||
private String removeSeverity(String msg) {
|
||||
return msg.replaceFirst("[^ ]+ ", "");
|
||||
}
|
||||
@@ -846,14 +893,10 @@ 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.setCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
if (!state.getAndSetCircuitBuilt(true)) LOG.info("Circuit built");
|
||||
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
||||
if (state.setCircuitBuilt(false)) {
|
||||
if (state.getAndSetCircuitBuilt(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
|
||||
@@ -896,7 +939,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
if (e instanceof ConnectionClosedEvent) {
|
||||
ConnectionClosedEvent c = (ConnectionClosedEvent) e;
|
||||
if (c.getTransportId().equals(getId())
|
||||
&& !c.isIncoming() && c.isException()) {
|
||||
LOG.info("Outgoing connection closed with exception");
|
||||
// The failure may indicate that the network is unstable
|
||||
state.resetNetworkStability();
|
||||
}
|
||||
} else if (e instanceof SettingsUpdatedEvent) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString())) {
|
||||
LOG.info("Tor settings updated");
|
||||
@@ -913,6 +964,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void disableAndReenableNetwork() {
|
||||
connectionStatusExecutor.execute(() -> {
|
||||
try {
|
||||
if (state.isTorRunning()) enableNetwork(false);
|
||||
} catch (IOException ex) {
|
||||
logException(LOG, WARNING, ex);
|
||||
}
|
||||
});
|
||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||
batteryManager.isCharging());
|
||||
}
|
||||
|
||||
private void updateConnectionStatus(NetworkStatus status,
|
||||
boolean charging) {
|
||||
connectionStatusExecutor.execute(() -> {
|
||||
@@ -944,8 +1007,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
int reasonsDisabled = 0;
|
||||
boolean enableNetwork = false, enableConnectionPadding = false;
|
||||
List<BridgeType> bridgeTypes = emptyList();
|
||||
boolean enableNetwork = false, enableBridges = false;
|
||||
boolean enableConnectionPadding = false;
|
||||
List<BridgeType> bridgeTypes =
|
||||
circumventionProvider.getSuitableBridgeTypes(country);
|
||||
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
@@ -974,12 +1039,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
enableNetwork = true;
|
||||
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
||||
(automatic && bridgesWork)) {
|
||||
if (ipv6Only) {
|
||||
bridgeTypes = asList(MEEK, SNOWFLAKE);
|
||||
} else {
|
||||
bridgeTypes = circumventionProvider
|
||||
.getSuitableBridgeTypes(country);
|
||||
}
|
||||
if (ipv6Only) bridgeTypes = singletonList(MEEK);
|
||||
enableBridges = true;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Using bridge types " + bridgeTypes);
|
||||
}
|
||||
@@ -999,9 +1060,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
try {
|
||||
if (enableNetwork) {
|
||||
enableBridges(bridgeTypes, country);
|
||||
enableBridges(enableBridges, bridgeTypes);
|
||||
enableConnectionPadding(enableConnectionPadding);
|
||||
enableIpv6(ipv6Only);
|
||||
useIpv6(ipv6Only);
|
||||
}
|
||||
enableNetwork(enableNetwork);
|
||||
} catch (IOException e) {
|
||||
@@ -1011,7 +1072,6 @@ 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) {
|
||||
@@ -1019,11 +1079,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void enableIpv6(boolean enable) throws IOException {
|
||||
if (!state.enableIpv6(enable)) return; // Unchanged
|
||||
private void useIpv6(boolean ipv6Only) throws IOException {
|
||||
try {
|
||||
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
|
||||
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
|
||||
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
|
||||
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
|
||||
} catch (TorNotRunningException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -1038,8 +1097,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
stopped = false,
|
||||
networkInitialised = false,
|
||||
networkEnabled = false,
|
||||
paddingEnabled = false,
|
||||
ipv6Enabled = false,
|
||||
bootstrapped = false,
|
||||
circuitBuilt = false,
|
||||
settingsChecked = false;
|
||||
@@ -1047,6 +1104,13 @@ 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;
|
||||
@@ -1054,9 +1118,6 @@ 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());
|
||||
@@ -1077,66 +1138,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private synchronized void setBootstrapped() {
|
||||
boolean wasBootstrapped = bootstrapped;
|
||||
bootstrapped = true;
|
||||
if (!wasBootstrapped) callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `circuitBuilt` flag and returns true if the flag has
|
||||
* changed.
|
||||
*/
|
||||
private synchronized boolean setCircuitBuilt(boolean built) {
|
||||
if (built == circuitBuilt) return false; // Unchanged
|
||||
circuitBuilt = built;
|
||||
callback.pluginStateChanged(getState());
|
||||
return true; // Changed
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the `networkEnabled` flag and returns true if the flag has
|
||||
* changed.
|
||||
*/
|
||||
private synchronized boolean enableNetwork(boolean enable) {
|
||||
boolean wasInitialised = networkInitialised;
|
||||
boolean wasEnabled = networkEnabled;
|
||||
private synchronized boolean getAndSetCircuitBuilt(boolean built) {
|
||||
boolean old = circuitBuilt;
|
||||
circuitBuilt = built;
|
||||
if (built != old) callback.pluginStateChanged(getState());
|
||||
return old;
|
||||
}
|
||||
|
||||
private synchronized void enableNetwork(boolean enable) {
|
||||
networkInitialised = true;
|
||||
networkEnabled = enable;
|
||||
if (!enable) circuitBuilt = false;
|
||||
if (!wasInitialised || enable != wasEnabled) {
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
return enable != wasEnabled;
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
private synchronized void setReasonsDisabled(int reasonsDisabled) {
|
||||
settingsChecked = true;
|
||||
int oldReasons = reasonsDisabled;
|
||||
reasonsDisabled = reasons;
|
||||
if (!wasChecked || reasons != oldReasons) {
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
this.reasonsDisabled = reasonsDisabled;
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
// Doesn't affect getState()
|
||||
@@ -1151,17 +1174,6 @@ 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;
|
||||
@@ -1193,6 +1205,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
logOrConnections();
|
||||
if (orConnectionsConnected == 0 && oldConnected != 0) {
|
||||
resetNetworkStability();
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
}
|
||||
@@ -1203,5 +1216,43 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ 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;
|
||||
@@ -41,16 +39,12 @@ 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;
|
||||
@@ -66,7 +60,6 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
@@ -81,7 +74,6 @@ 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;
|
||||
@@ -95,7 +87,7 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
|
||||
@Nullable
|
||||
abstract String getArchitectureForTorBinary();
|
||||
|
||||
abstract TorPlugin createPluginInstance(Backoff backoff,
|
||||
abstract TorPlugin createPluginInstance(
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture);
|
||||
|
||||
@@ -122,11 +114,9 @@ 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(backoff, torRendezvousCrypto,
|
||||
TorPlugin plugin = createPluginInstance(torRendezvousCrypto,
|
||||
callback, architecture);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
|
||||
@@ -44,14 +44,12 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
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.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;
|
||||
@@ -234,19 +232,11 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof MessageSharedEvent) {
|
||||
MessageSharedEvent m = (MessageSharedEvent) e;
|
||||
// If the contact is present in the map (ie the value is not null)
|
||||
// and the value is true, the message's group is shared with the
|
||||
// contact and therefore the message may now be sendable
|
||||
if (m.getGroupVisibility().get(contactId) == TRUE) {
|
||||
generateOffer();
|
||||
}
|
||||
generateOffer();
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getVisibility() == SHARED &&
|
||||
g.getAffectedContacts().contains(contactId)) {
|
||||
if (g.getAffectedContacts().contains(contactId))
|
||||
generateOffer();
|
||||
}
|
||||
} else if (e instanceof MessageRequestedEvent) {
|
||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||
generateBatch();
|
||||
@@ -320,8 +310,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
Collection<Message> batch =
|
||||
db.generateRequestedBatch(txn, contactId,
|
||||
BATCH_CAPACITY, maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId,
|
||||
maxLatency));
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
return batch;
|
||||
});
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -364,8 +353,7 @@ 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,
|
||||
maxLatency));
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
return offer;
|
||||
});
|
||||
if (LOG.isLoggable(INFO))
|
||||
|
||||
@@ -7,16 +7,14 @@ 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
@@ -31,7 +29,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
|
||||
|
||||
/**
|
||||
* A {@link SimplexOutgoingSession} for sending and acking messages via a
|
||||
* mailbox. The session uses a {@link OutgoingSessionRecord} to record the IDs
|
||||
* mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
|
||||
* of the messages sent and acked during the session so that they can be
|
||||
* recorded in the DB as sent or acked after the file has been successfully
|
||||
* uploaded to the mailbox.
|
||||
@@ -43,7 +41,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||
private static final Logger LOG =
|
||||
getLogger(MailboxOutgoingSession.class.getName());
|
||||
|
||||
private final OutgoingSessionRecord sessionRecord;
|
||||
private final DeferredSendHandler deferredSendHandler;
|
||||
private final long initialCapacity;
|
||||
|
||||
MailboxOutgoingSession(DatabaseComponent db,
|
||||
@@ -53,42 +51,36 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||
long maxLatency,
|
||||
StreamWriter streamWriter,
|
||||
SyncRecordWriter recordWriter,
|
||||
OutgoingSessionRecord sessionRecord,
|
||||
DeferredSendHandler deferredSendHandler,
|
||||
long capacity) {
|
||||
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
|
||||
recordWriter);
|
||||
this.sessionRecord = sessionRecord;
|
||||
this.deferredSendHandler = deferredSendHandler;
|
||||
this.initialCapacity = capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendAcks() throws DbException, IOException {
|
||||
List<MessageId> idsToAck = loadMessageIdsToAck();
|
||||
int idsSent = 0;
|
||||
while (idsSent < idsToAck.size() && !isInterrupted()) {
|
||||
int idsRemaining = idsToAck.size() - idsSent;
|
||||
long capacity = getRemainingCapacity();
|
||||
long idCapacity =
|
||||
(capacity - RECORD_HEADER_BYTES) / MessageId.LENGTH;
|
||||
if (idCapacity == 0) break; // Out of capacity
|
||||
int idsInRecord = (int) min(idCapacity, MAX_MESSAGE_IDS);
|
||||
int idsToSend = min(idsRemaining, idsInRecord);
|
||||
List<MessageId> acked =
|
||||
idsToAck.subList(idsSent, idsSent + idsToSend);
|
||||
recordWriter.writeAck(new Ack(acked));
|
||||
sessionRecord.onAckSent(acked);
|
||||
while (!isInterrupted()) {
|
||||
Collection<MessageId> idsToAck = loadMessageIdsToAck();
|
||||
if (idsToAck.isEmpty()) break;
|
||||
recordWriter.writeAck(new Ack(idsToAck));
|
||||
deferredSendHandler.onAckSent(idsToAck);
|
||||
LOG.info("Sent ack");
|
||||
idsSent += idsToSend;
|
||||
}
|
||||
}
|
||||
|
||||
private List<MessageId> loadMessageIdsToAck() throws DbException {
|
||||
private Collection<MessageId> loadMessageIdsToAck() throws DbException {
|
||||
long idCapacity = (getRemainingCapacity() - RECORD_HEADER_BYTES)
|
||||
/ MessageId.LENGTH;
|
||||
if (idCapacity <= 0) return emptyList(); // Out of capacity
|
||||
int maxMessageIds = (int) min(idCapacity, MAX_MESSAGE_IDS);
|
||||
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
|
||||
db.getMessagesToAck(txn, contactId));
|
||||
db.getMessagesToAck(txn, contactId, maxMessageIds));
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(ids.size() + " messages to ack");
|
||||
}
|
||||
return new ArrayList<>(ids);
|
||||
return ids;
|
||||
}
|
||||
|
||||
private long getRemainingCapacity() {
|
||||
@@ -104,7 +96,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
||||
db.getMessageToSend(txn, contactId, m, maxLatency, false));
|
||||
if (message == null) continue; // No longer shared
|
||||
recordWriter.writeMessage(message);
|
||||
sessionRecord.onMessageSent(m);
|
||||
deferredSendHandler.onMessageSent(m);
|
||||
LOG.info("Sent message");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -26,8 +25,6 @@ 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 {
|
||||
@@ -76,18 +73,6 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, StreamWriter streamWriter,
|
||||
OutgoingSessionRecord sessionRecord) {
|
||||
OutputStream out = streamWriter.getOutputStream();
|
||||
SyncRecordWriter recordWriter =
|
||||
recordWriterFactory.createRecordWriter(out);
|
||||
return new MailboxOutgoingSession(db, eventBus, c, t, maxLatency,
|
||||
streamWriter, recordWriter, sessionRecord,
|
||||
MAX_FILE_PAYLOAD_BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||
|
||||
@@ -11,26 +11,23 @@ d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=s
|
||||
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
||||
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
|
||||
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
|
||||
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
|
||||
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
|
||||
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
|
||||
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
|
||||
n Bridge obfs4 87.121.72.109:9002 C8081D4731C953FA4AE166946E72B29153351E34 cert=bikAqxKV6Ch5gFCBTdPI28VeShYa1ZgkLmvc7YZNLWFsFZoaCULL/3AQKjpIfvSiJs5jGQ iat-mode=0
|
||||
n Bridge obfs4 70.34.249.113:443 F441B16ABB1055794C2CE01821FC05047B2C8CFC cert=MauLNoyq8EwjY4Qe0oASYzs2hXdSjNgy+BtP9oo1naHhRsyKTtAZzeNv08RnzWjMJrTwcg iat-mode=0
|
||||
n Bridge obfs4 104.168.68.90:443 ED55B3C321E44EA7E50EF568C8A63CF75E89A58C cert=fgonxDvltTp8nmcOE9sUG94eOAALxETVVXAwnTZJLPpf7rjPuTp+abKl4VyFkxfcLRr5KQ iat-mode=0
|
||||
n Bridge obfs4 158.247.207.151:443 6170ADBBB6C1859A8E7E4416BB8AB3AF471AE47F cert=Od4izlwLnXcq7LMSOJtnZLtklaUn+X+gPcBwN7RUEkk9rqxRRYNHW7as8g6+jheDsazxAQ iat-mode=0
|
||||
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
|
||||
n Bridge obfs4 85.214.28.204:47111 78A36E46BB082A471848239D3F4390A8F8C6084D cert=96sr3eaUFBDu4wFVAQIfNFElh0UNuutJ/3/Fh2Vu2PHfacQ8bwfY02bwG351U8BZpLnfUQ iat-mode=0
|
||||
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
|
||||
n Bridge obfs4 82.39.132.97:6969 F505EF4C41C77FFDC0C440C122A02129FBE25823 cert=bwNWuL7UYB9aiKajE1gkffylYx/EM4FjSZxIJ0pVT/xaR21xXlIdaXw7l+EYmC4nVIh2HQ iat-mode=0
|
||||
n Bridge obfs4 185.103.252.72:443 75F15E9339FF572F88F5588D429FEA379744BC53 cert=nOZ/SaRE3L1dChvjfe0Ks/wM/F8iFhwd3g2G5zgtcLB8x+wiZRWCwjRrbbiQyb3Gh2mxRQ iat-mode=0
|
||||
n Bridge obfs4 185.177.207.13:22662 928C1E4289A01F34C8FB423FC32C0E77EE0F8736 cert=p9L6+25s8bnfkye1ZxFeAE4mAGY7DH4Gaj7dxngIIzP9BtqrHHwZXdjMK0RVIQ34C7aqZw iat-mode=2
|
||||
n Bridge obfs4 207.181.229.55:40132 37FE8D782F5DD2BAEEDAAB8257B701344676B6DD cert=f5Hbfn3ToMzH170cV8DfLly3vRynveidfOfDcbradIDtbLDX15V2yQ8eEH2CPKQJmQR2Hg 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
|
||||
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
|
||||
v Bridge 185.189.195.124:8199 A1F3EE78F9C2343668E68FEB84358A4C742831A5
|
||||
v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB
|
||||
v Bridge 75.100.92.154:22815 465E990FA8A752DDE861EDF31E42454ACC037AB4
|
||||
m Bridge meek_lite 192.0.2.2:80 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
s Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72
|
||||
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
@@ -1,4 +0,0 @@
|
||||
ZZ 1 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478
|
||||
ZZ 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478
|
||||
TM 1 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479
|
||||
TM 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -58,10 +57,6 @@ 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() {{
|
||||
|
||||
@@ -10,8 +10,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
|
||||
import org.briarproject.bramble.test.TestDnsModule;
|
||||
import org.briarproject.bramble.test.TestSocksModule;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -22,9 +20,7 @@ import dagger.Component;
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
BrambleCoreIntegrationTestModule.class,
|
||||
BrambleCoreModule.class,
|
||||
TestDnsModule.class,
|
||||
TestSocksModule.class
|
||||
BrambleCoreModule.class
|
||||
})
|
||||
interface ContactExchangeIntegrationTestComponent
|
||||
extends BrambleCoreIntegrationTestEagerSingletons {
|
||||
|
||||
@@ -17,6 +17,7 @@ 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;
|
||||
@@ -62,9 +63,13 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
private final boolean alice = new Random().nextBoolean();
|
||||
|
||||
private final ContactManagerImpl contactManager =
|
||||
new ContactManagerImpl(db, keyManager, identityManager,
|
||||
pendingContactFactory);
|
||||
private ContactManagerImpl contactManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
contactManager = new ContactManagerImpl(db, keyManager,
|
||||
identityManager, pendingContactFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddContact() throws Exception {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -48,7 +47,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testDigestWithKeyedTestVectors() throws FormatException {
|
||||
public void testDigestWithKeyedTestVectors() {
|
||||
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
||||
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
|
||||
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
|
||||
@@ -64,8 +63,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigestWithKeyedTestVectorsAndRandomUpdate()
|
||||
throws FormatException {
|
||||
public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
||||
@@ -140,7 +138,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runSelfTest() throws FormatException {
|
||||
public void runSelfTest() {
|
||||
Blake2bDigest testDigest = new Blake2bDigest(256);
|
||||
byte[] md = new byte[64];
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -84,7 +83,7 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRfc7748TestVector() throws FormatException {
|
||||
public void testRfc7748TestVector() {
|
||||
byte[] aPriv = parsePrivateKey(ALICE_PRIVATE);
|
||||
byte[] aPub = fromHexString(ALICE_PUBLIC);
|
||||
byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
|
||||
@@ -98,8 +97,7 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivesSameSharedSecretFromEquivalentPublicKey()
|
||||
throws FormatException {
|
||||
public void testDerivesSameSharedSecretFromEquivalentPublicKey() {
|
||||
byte[] aPub = fromHexString(ALICE_PUBLIC);
|
||||
byte[] bPriv = parsePrivateKey(BOB_PRIVATE);
|
||||
byte[] sharedSecret = fromHexString(SHARED_SECRET);
|
||||
@@ -170,7 +168,7 @@ public class KeyAgreementTest extends BrambleTestCase {
|
||||
return crypto.deriveSharedSecret(label, publicKey, keyPair, inputs);
|
||||
}
|
||||
|
||||
private byte[] parsePrivateKey(String hex) throws FormatException {
|
||||
private byte[] parsePrivateKey(String hex) {
|
||||
// Private keys need to be clamped because curve25519-java does the
|
||||
// clamping at key generation time, not multiplication time
|
||||
return AgreementKeyParser.clamp(fromHexString(hex));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -16,11 +15,11 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
|
||||
|
||||
// Test vectors from the NaCl paper
|
||||
// http://cr.yp.to/highspeed/naclcrypto-20090310.pdf
|
||||
private final byte[] TEST_KEY = StringUtils.fromHexString(
|
||||
private static final byte[] TEST_KEY = StringUtils.fromHexString(
|
||||
"1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389");
|
||||
private final byte[] TEST_IV = StringUtils.fromHexString(
|
||||
private static final byte[] TEST_IV = StringUtils.fromHexString(
|
||||
"69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37");
|
||||
private final byte[] TEST_PLAINTEXT = StringUtils.fromHexString(
|
||||
private static final byte[] TEST_PLAINTEXT = StringUtils.fromHexString(
|
||||
"be075fc53c81f2d5cf141316" +
|
||||
"ebeb0c7b5228c52a4c62cbd4" +
|
||||
"4b66849b64244ffce5ecbaaf" +
|
||||
@@ -32,7 +31,7 @@ public class XSalsa20Poly1305AuthenticatedCipherTest extends BrambleTestCase {
|
||||
"048977eb48f59ffd4924ca1c" +
|
||||
"60902e52f0a089bc76897040" +
|
||||
"e082f937763848645e0705");
|
||||
private final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString(
|
||||
private static final byte[] TEST_CIPHERTEXT = StringUtils.fromHexString(
|
||||
"f3ffc7703f9400e52a7dfb4b" +
|
||||
"3d3305d98e993b9f48681273" +
|
||||
"c29650ba32fc76ce48332ea7" +
|
||||
@@ -47,10 +46,6 @@ 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);
|
||||
|
||||
@@ -618,12 +618,11 @@ public class BdfReaderImplTest extends BrambleTestCase {
|
||||
r.readDictionary();
|
||||
}
|
||||
|
||||
private void setContents(String hex) throws FormatException {
|
||||
private void setContents(String hex) {
|
||||
setContents(hex, DEFAULT_MAX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
private void setContents(String hex, int maxBufferSize)
|
||||
throws FormatException {
|
||||
private void setContents(String hex, int maxBufferSize) {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(fromHexString(hex));
|
||||
r = new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, maxBufferSize);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -16,8 +17,14 @@ import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
private final BdfWriterImpl w = new BdfWriterImpl(out);
|
||||
private ByteArrayOutputStream out = null;
|
||||
private BdfWriterImpl w = null;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
out = new ByteArrayOutputStream();
|
||||
w = new BdfWriterImpl(out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteNull() throws IOException {
|
||||
|
||||
@@ -70,7 +70,6 @@ import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||
@@ -284,16 +283,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
temporary, null);
|
||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// Broadcast events for message being added and changing state
|
||||
// The message was added, so the listeners should be called
|
||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
MessageStateChangedEvent.class)));
|
||||
// If message is shared, get group visibility and broadcast event
|
||||
if (shared) {
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(singletonMap(contactId, true)));
|
||||
if (shared)
|
||||
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
||||
}
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
@@ -308,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(27).of(database).startTransaction();
|
||||
exactly(25).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(27).of(database).containsContact(txn, contactId);
|
||||
exactly(25).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(27).of(database).abortTransaction(txn);
|
||||
exactly(25).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
@@ -326,23 +321,6 @@ 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));
|
||||
@@ -394,7 +372,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessagesToAck(transaction, contactId));
|
||||
db.getMessagesToAck(transaction, contactId, 123));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
@@ -699,11 +677,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the message is in the DB (which it's not)
|
||||
exactly(16).of(database).startTransaction();
|
||||
exactly(15).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(16).of(database).containsMessage(txn, messageId);
|
||||
exactly(15).of(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
exactly(16).of(database).abortTransaction(txn);
|
||||
exactly(15).of(database).abortTransaction(txn);
|
||||
// Allow other checks to pass
|
||||
allowing(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
@@ -727,14 +705,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.getGroupId(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessage(transaction, messageId));
|
||||
@@ -1409,7 +1379,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).lowerAckFlag(txn, contactId, acked);
|
||||
// First message is still visible to the contact - flag lowered
|
||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||
will(returnValue(true));
|
||||
// Second message is no longer visible - flag not lowered
|
||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
|
||||
will(returnValue(false));
|
||||
oneOf(database)
|
||||
.lowerAckFlag(txn, contactId, singletonList(messageId));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
@@ -1886,16 +1863,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getMessageDependents(txn, messageId);
|
||||
// Broadcast events for message being added and changing state
|
||||
// broadcast for message added event
|
||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
MessageStateChangedEvent.class)));
|
||||
// If message is shared, get group visibility and broadcast event
|
||||
if (shared) {
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(singletonMap(contactId, true)));
|
||||
if (shared)
|
||||
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
||||
}
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// close()
|
||||
|
||||
@@ -168,7 +168,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(db.containsContact(txn, contactId));
|
||||
assertTrue(db.containsGroup(txn, groupId));
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
assertEquals(groupId, db.getGroupId(txn, messageId));
|
||||
assertArrayEquals(message.getBody(),
|
||||
db.getMessage(txn, messageId).getBody());
|
||||
|
||||
@@ -379,9 +378,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Initially there should be nothing to send
|
||||
assertFalse(
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertFalse(
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
|
||||
// Add some messages to ack
|
||||
Message message1 = getMessage(groupId);
|
||||
@@ -390,7 +389,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||
|
||||
// Both message IDs should be returned
|
||||
assertTrue(db.containsAcksToSend(txn, contactId));
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
assertEquals(asList(messageId, messageId1), ids);
|
||||
|
||||
@@ -398,7 +400,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||
|
||||
// No message IDs should be returned
|
||||
assertFalse(db.containsAcksToSend(txn, contactId));
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertFalse(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
|
||||
|
||||
// Raise the ack flag again
|
||||
@@ -406,7 +411,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
|
||||
// Both message IDs should be returned
|
||||
assertTrue(db.containsAcksToSend(txn, contactId));
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
assertTrue(
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
assertEquals(asList(messageId, messageId1), ids);
|
||||
|
||||
@@ -2021,51 +2029,37 @@ 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, MAX_LATENCY));
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// 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, MAX_LATENCY));
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// 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, MAX_LATENCY));
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Share the message - now it should be sendable immediately
|
||||
db.setMessageShared(txn, messageId, true);
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Mark the message as requested - it should still be sendable
|
||||
db.raiseRequestedFlag(txn, contactId, messageId);
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Update the message's expiry time as though we sent it - now the
|
||||
// message should be sendable after one round-trip
|
||||
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));
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Update the message's expiry time again - now it should be sendable
|
||||
// after two round-trips
|
||||
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));
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Delete the message - there should be no messages to send
|
||||
db.deleteMessage(txn, messageId);
|
||||
assertEquals(Long.MAX_VALUE,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -2130,8 +2124,7 @@ 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, MAX_LATENCY));
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Time: now + MAX_LATENCY * 2 - 1
|
||||
// The message should not yet be sendable
|
||||
@@ -2174,8 +2167,7 @@ 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, MAX_LATENCY));
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// The message should not be sendable via the same transport
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
@@ -2222,8 +2214,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2,
|
||||
db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Time: now + MAX_LATENCY * 2 - 1
|
||||
// The message should not yet be sendable
|
||||
@@ -2234,8 +2225,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Reset the retransmission times
|
||||
db.resetUnackedMessagesToSend(txn, contactId);
|
||||
|
||||
// The message should be sendable immediately
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
|
||||
// The message should have infinitely short expiry
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// The message should be sendable
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
@@ -2588,7 +2579,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertNothingToSendLazily(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertFalse(
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
@@ -2599,7 +2590,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertOneMessageToSendLazily(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertTrue(
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
@@ -2610,7 +2601,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertNothingToSendEagerly(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertFalse(
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
Collection<MessageId> unacked =
|
||||
db.getUnackedMessagesToSend(txn, contactId);
|
||||
assertTrue(unacked.isEmpty());
|
||||
@@ -2620,7 +2611,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private void assertOneMessageToSendEagerly(Database<Connection> db,
|
||||
Connection txn) throws Exception {
|
||||
assertTrue(
|
||||
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
|
||||
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||
Collection<MessageId> unacked =
|
||||
db.getUnackedMessagesToSend(txn, contactId);
|
||||
assertEquals(singletonList(messageId), unacked);
|
||||
|
||||
@@ -14,6 +14,7 @@ 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;
|
||||
@@ -40,8 +41,13 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
|
||||
private final KeyPair handshakeKeyPair =
|
||||
new KeyPair(handshakePublicKey, handshakePrivateKey);
|
||||
|
||||
private final IdentityManagerImpl identityManager =
|
||||
new IdentityManagerImpl(db, crypto, authorFactory, clock);
|
||||
private IdentityManagerImpl identityManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
identityManager =
|
||||
new IdentityManagerImpl(db, crypto, authorFactory, clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenDatabaseIdentityRegistered() throws Exception {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user