Compare commits

..

1 Commits

Author SHA1 Message Date
Sebastian Kürten
426909f735 Make strict mode warnings configurable 2022-06-29 15:15:29 +02:00
148 changed files with 1810 additions and 7877 deletions

View File

@@ -98,7 +98,7 @@ bridge test:
allow_failure: true allow_failure: true
script: script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest - OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 4h timeout: 3h
mailbox integration test: mailbox integration test:
extends: .optional_tests extends: .optional_tests

View File

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

View File

@@ -15,8 +15,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10412 versionCode 10409
versionName "1.4.12" versionName "1.4.9"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -42,10 +42,8 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
implementation 'androidx.annotation:annotation:1.5.0'
tor "org.briarproject:tor-android:$tor_version" tor "org.briarproject:tor-android:$tor_version"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version" tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
tor "org.briarproject:snowflake-android:$snowflake_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

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

View File

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

View File

@@ -31,8 +31,6 @@ import java.util.zip.ZipInputStream;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import androidx.annotation.ChecksSdkIntAtLeast;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -47,14 +45,13 @@ class AndroidTorPlugin extends TorPlugin {
private static final String TOR_LIB_NAME = "libtor.so"; private static final String TOR_LIB_NAME = "libtor.so";
private static final String OBFS4_LIB_NAME = "libobfs4proxy.so"; private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so";
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidTorPlugin.class.getName()); getLogger(AndroidTorPlugin.class.getName());
private final Application app; private final Application app;
private final AndroidWakeLock wakeLock; private final AndroidWakeLock wakeLock;
private final File torLib, obfs4Lib, snowflakeLib; private final File torLib, obfs4Lib;
AndroidTorPlugin(Executor ioExecutor, AndroidTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor, Executor wakefulIoExecutor,
@@ -86,7 +83,6 @@ class AndroidTorPlugin extends TorPlugin {
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir; String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
torLib = new File(nativeLibDir, TOR_LIB_NAME); torLib = new File(nativeLibDir, TOR_LIB_NAME);
obfs4Lib = new File(nativeLibDir, OBFS4_LIB_NAME); obfs4Lib = new File(nativeLibDir, OBFS4_LIB_NAME);
snowflakeLib = new File(nativeLibDir, SNOWFLAKE_LIB_NAME);
} }
@Override @Override
@@ -112,12 +108,6 @@ class AndroidTorPlugin extends TorPlugin {
if (!enable) wakeLock.release(); if (!enable) wakeLock.release();
} }
@Override
@ChecksSdkIntAtLeast(api = 25)
protected boolean canVerifyLetsEncryptCerts() {
return SDK_INT >= 25;
}
@Override @Override
public void stop() { public void stop() {
super.stop(); super.stop();
@@ -134,43 +124,39 @@ class AndroidTorPlugin extends TorPlugin {
return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile(); return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
} }
@Override
protected File getSnowflakeExecutableFile() {
return snowflakeLib.exists()
? snowflakeLib : super.getSnowflakeExecutableFile();
}
@Override @Override
protected void installTorExecutable() throws IOException { 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 @Override
protected void installObfs4Executable() throws IOException { protected void installObfs4Executable() throws IOException {
installExecutable(super.getObfs4ExecutableFile(), obfs4Lib, File extracted = super.getObfs4ExecutableFile();
OBFS4_LIB_NAME); if (obfs4Lib.exists()) {
} // If an older version left behind an obfs4 binary, delete it
@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
if (extracted.exists()) { if (extracted.exists()) {
if (extracted.delete()) LOG.info("Deleted old binary"); if (extracted.delete()) LOG.info("Deleted obfs4 binary");
else LOG.info("Failed to delete old binary"); else LOG.info("Failed to delete obfs4 binary");
} }
} else if (SDK_INT < 29) { } else if (SDK_INT < 29) {
// The binary wasn't extracted at install time. Try to extract it // The binary wasn't extracted at install time. Try to extract it
extractLibraryFromApk(libName, extracted); extractLibraryFromApk(OBFS4_LIB_NAME, extracted);
} else { } else {
// No point extracting the binary, we won't be allowed to execute it // No point extracting the binary, we won't be allowed to execute it
throw new FileNotFoundException(lib.getAbsolutePath()); throw new FileNotFoundException(obfs4Lib.getAbsolutePath());
} }
} }

View File

@@ -1,6 +1,5 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'androidx.annotation:annotation:1.5.0:annotation-1.5.0.jar:261fb7c0210858500bab66d34354972a75166ab4182add283780b05513d6ec4a',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db', '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: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', '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.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:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', '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:obfs4proxy-android:0.0.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89',
'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293', 'org.briarproject:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9',
'org.briarproject:tor-android:0.4.5.14:tor-android-0.4.5.14.jar:7cf1beaa6c1db51fc8fac263aba9624ef289c3db29772509efcbc59f7057330a',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', '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:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4', '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-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.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.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-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-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.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.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.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.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09', 'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',

View File

@@ -283,13 +283,6 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
Group getGroup(Transaction txn, GroupId g) throws DbException; Group getGroup(Transaction txn, GroupId g) throws DbException;
/**
* Returns the ID of the group containing the given message.
* <p/>
* Read-only.
*/
GroupId getGroupId(Transaction txn, MessageId m) throws DbException;
/** /**
* Returns the metadata for the given group. * Returns the metadata for the given group.
* <p/> * <p/>
@@ -356,13 +349,13 @@ public interface DatabaseComponent extends TransactionManager {
Metadata query) throws DbException; Metadata query) throws DbException;
/** /**
* Returns the IDs of all messages received from the given contact that * Returns the IDs of some messages received from the given contact that
* need to be acknowledged. * need to be acknowledged, up to the given number of messages.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c) Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
throws DbException; int maxMessages) throws DbException;
/** /**
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
@@ -499,8 +492,6 @@ public interface DatabaseComponent extends TransactionManager {
* Returns the message with the given ID for transmission to the given * Returns the message with the given ID for transmission to the given
* contact over a transport with the given maximum latency. Returns null * contact over a transport with the given maximum latency. Returns null
* if the message is no longer visible to the contact. * if the message is no longer visible to the contact.
* <p/>
* Read-only if {@code markAsSent} is false.
* *
* @param markAsSent True if the message should be marked as sent. * @param markAsSent True if the message should be marked as sent.
* If false it can be marked as sent by calling * If false it can be marked as sent by calling

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import java.util.List; import java.util.List;
@@ -76,23 +75,4 @@ public class MailboxProperties {
public MailboxFolderId getOutboxId() { public MailboxFolderId getOutboxId() {
return outboxId; return outboxId;
} }
@Override
public boolean equals(Object o) {
if (o instanceof MailboxProperties) {
MailboxProperties m = (MailboxProperties) o;
return owner == m.owner &&
onion.equals(m.onion) &&
authToken.equals(m.authToken) &&
NullSafety.equals(inboxId, m.inboxId) &&
NullSafety.equals(outboxId, m.outboxId) &&
serverSupports.equals(m.serverSupports);
}
return false;
}
@Override
public int hashCode() {
return authToken.hashCode();
}
} }

View File

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

View File

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

View File

@@ -79,12 +79,6 @@ public interface MailboxUpdateManager {
*/ */
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports"; String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
/**
* Key in the client's local group for storing the serverSupports list that
* was last sent out, if any.
*/
String GROUP_KEY_SENT_SERVER_SUPPORTS = "sentServerSupports";
/** /**
* Returns the latest {@link MailboxUpdate} sent to the given contact. * Returns the latest {@link MailboxUpdate} sent to the given contact.
* <p> * <p>

View File

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

View File

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

View File

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

View File

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

View File

@@ -338,17 +338,6 @@ public class TestUtils {
return false; return false;
} }
public static <E extends Event> E getEvent(Transaction txn,
Class<E> eventClass) {
for (CommitAction action : txn.getActions()) {
if (action instanceof EventAction) {
Event event = ((EventAction) action).getEvent();
if (eventClass.isInstance(event)) return eventClass.cast(event);
}
}
throw new AssertionError();
}
public static boolean isCryptoStrengthUnlimited() { public static boolean isCryptoStrengthUnlimited() {
try { try {
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding")

View File

@@ -16,7 +16,7 @@ dependencies {
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.5' implementation 'org.briarproject:jtorctl:0.4'
//noinspection GradleDependency //noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version"

View File

@@ -52,7 +52,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.sort;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
@@ -475,8 +474,6 @@ class ClientHelperImpl implements ClientHelper {
list.add(new MailboxVersion(element.getLong(0).intValue(), list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue())); element.getLong(1).intValue()));
} }
// Sort the list of versions for easier comparison
sort(list);
return list; return list;
} }

View File

@@ -320,13 +320,6 @@ interface Database<T> {
*/ */
Group getGroup(T txn, GroupId g) throws DbException; Group getGroup(T txn, GroupId g) throws DbException;
/**
* Returns the ID of the group containing the given message.
* <p/>
* Read-only.
*/
GroupId getGroupId(T txn, MessageId m) throws DbException;
/** /**
* Returns the metadata for the given group. * Returns the metadata for the given group.
* <p/> * <p/>
@@ -352,11 +345,8 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs of all contacts for which the given group's visibility * Returns the IDs of all contacts to which the given group's visibility is
* is either {@link Visibility#SHARED shared} or * either {@link Visibility VISIBLE} or {@link Visibility SHARED}.
* {@link Visibility#VISIBLE visible}. The value in the map is true if the
* group is {@link Visibility#SHARED shared} or false if the group is
* {@link Visibility#VISIBLE visible}.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */

View File

@@ -287,12 +287,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
if (shared) { if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
Map<ContactId, Boolean> visibility =
db.getGroupVisibility(txn, m.getGroupId());
transaction.attach(new MessageSharedEvent(m.getId(),
m.getGroupId(), visibility));
}
} }
db.mergeMessageMetadata(txn, m.getId(), meta); db.mergeMessageMetadata(txn, m.getId(), meta);
} }
@@ -555,15 +550,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getGroup(txn, g); return db.getGroup(txn, g);
} }
@Override
public GroupId getGroupId(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
return db.getGroupId(txn, m);
}
@Override @Override
public Metadata getGroupMetadata(Transaction transaction, GroupId g) public Metadata getGroupMetadata(Transaction transaction, GroupId g)
throws DbException { throws DbException {
@@ -634,11 +620,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public Collection<MessageId> getMessagesToAck(Transaction transaction, public Collection<MessageId> getMessagesToAck(Transaction transaction,
ContactId c) throws DbException { ContactId c, int maxMessages) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
return db.getMessagesToAck(txn, c, Integer.MAX_VALUE); return db.getMessagesToAck(txn, c, maxMessages);
} }
@Override @Override
@@ -760,9 +746,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
public Message getMessageToSend(Transaction transaction, ContactId c, public Message getMessageToSend(Transaction transaction, ContactId c,
MessageId m, long maxLatency, boolean markAsSent) MessageId m, long maxLatency, boolean markAsSent)
throws DbException { throws DbException {
if (markAsSent && transaction.isReadOnly()) { if (transaction.isReadOnly()) throw new IllegalArgumentException();
throw new IllegalArgumentException();
}
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
@@ -1113,7 +1097,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
db.lowerAckFlag(txn, c, acked); List<MessageId> visible = new ArrayList<>(acked.size());
for (MessageId m : acked) {
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
}
db.lowerAckFlag(txn, c, visible);
} }
@Override @Override
@@ -1196,9 +1184,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (db.getMessageState(txn, m) != DELIVERED) if (db.getMessageState(txn, m) != DELIVERED)
throw new IllegalArgumentException("Shared undelivered message"); throw new IllegalArgumentException("Shared undelivered message");
db.setMessageShared(txn, m, true); db.setMessageShared(txn, m, true);
GroupId g = db.getGroupId(txn, m); transaction.attach(new MessageSharedEvent(m));
Map<ContactId, Boolean> visibility = db.getGroupVisibility(txn, g);
transaction.attach(new MessageSharedEvent(m, g, visibility));
} }
@Override @Override

View File

@@ -1683,27 +1683,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public GroupId getGroupId(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT groupId FROM messages WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
GroupId g = new GroupId(rs.getBytes(1));
rs.close();
ps.close();
return g;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<Group> getGroups(Connection txn, ClientId c, public Collection<Group> getGroups(Connection txn, ClientId c,
int majorVersion) throws DbException { int majorVersion) throws DbException {

View File

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

View File

@@ -10,6 +10,7 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -29,6 +30,7 @@ class ContactMailboxClient implements MailboxClient {
@Nullable @Nullable
private MailboxWorker uploadWorker = null, downloadWorker = null; private MailboxWorker uploadWorker = null, downloadWorker = null;
@Inject
ContactMailboxClient(MailboxWorkerFactory workerFactory, ContactMailboxClient(MailboxWorkerFactory workerFactory,
ConnectivityChecker connectivityChecker, ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor) { TorReachabilityMonitor reachabilityMonitor) {
@@ -55,10 +57,6 @@ class ContactMailboxClient implements MailboxClient {
} }
if (uploadWorker != null) uploadWorker.destroy(); if (uploadWorker != null) uploadWorker.destroy();
if (downloadWorker != null) downloadWorker.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 @Override

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@@ -34,7 +35,6 @@ import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES; import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static okhttp3.internal.Util.EMPTY_REQUEST; import static okhttp3.internal.Util.EMPTY_REQUEST;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@@ -125,8 +125,6 @@ class MailboxApiImpl implements MailboxApi {
if (major < 0 || minor < 0) throw new ApiException(); if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor)); serverSupports.add(new MailboxVersion(major, minor));
} }
// Sort the list of versions for easier comparison
sort(serverSupports);
return serverSupports; return serverSupports;
} }
@@ -218,11 +216,9 @@ class MailboxApiImpl implements MailboxApi {
@Override @Override
public List<MailboxFile> getFiles(MailboxProperties properties, public List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) MailboxFolderId folderId) throws IOException, ApiException {
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId; String path = "/files/" + folderId;
Response response = sendGetRequest(properties, path); Response response = sendGetRequest(properties, path);
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException(); if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body(); ResponseBody body = response.body();
@@ -247,7 +243,7 @@ class MailboxApiImpl implements MailboxApi {
if (time < 1) throw new ApiException(); if (time < 1) throw new ApiException();
list.add(new MailboxFile(MailboxFileId.fromString(name), time)); list.add(new MailboxFile(MailboxFileId.fromString(name), time));
} }
sort(list); Collections.sort(list);
return list; return list;
} catch (JacksonException | InvalidMailboxIdException e) { } catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException(); throw new ApiException();
@@ -256,11 +252,9 @@ class MailboxApiImpl implements MailboxApi {
@Override @Override
public void getFile(MailboxProperties properties, MailboxFolderId folderId, public void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) MailboxFileId fileId, File file) throws IOException, ApiException {
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId + "/" + fileId; String path = "/files/" + folderId + "/" + fileId;
Response response = sendGetRequest(properties, path); Response response = sendGetRequest(properties, path);
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException(); if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body(); ResponseBody body = response.body();

View File

@@ -24,10 +24,6 @@ interface MailboxClient {
/** /**
* Assigns a contact to the client for upload. * 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, void assignContactForUpload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId); MailboxFolderId folderId);
@@ -39,11 +35,6 @@ interface MailboxClient {
/** /**
* Assigns a contact to the client for download. * 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, void assignContactForDownload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId); MailboxFolderId folderId);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -120,8 +120,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
db.transaction(false, txn -> { db.transaction(false, txn -> {
mailboxSettingsManager mailboxSettingsManager
.setOwnMailboxProperties(txn, ownerProperties); .setOwnMailboxProperties(txn, ownerProperties);
mailboxSettingsManager.recordSuccessfulConnection(txn, time, mailboxSettingsManager.recordSuccessfulConnection(txn, time);
ownerProperties.getServerSupports());
// A (possibly new) mailbox is paired. Reset message retransmission // A (possibly new) mailbox is paired. Reset message retransmission
// timers for contacts who doesn't have their own mailbox. This way, // timers for contacts who doesn't have their own mailbox. This way,
// data stranded on our old mailbox will be re-uploaded to our new. // data stranded on our old mailbox will be re-uploaded to our new.

View File

@@ -80,7 +80,7 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
encodeServerSupports(serverSupports, s); encodeServerSupports(serverSupports, s);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p); hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
} }
} }
@@ -89,10 +89,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
Settings s = new Settings(); Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, ""); s.put(SETTINGS_KEY_ONION, "");
s.put(SETTINGS_KEY_TOKEN, ""); s.put(SETTINGS_KEY_TOKEN, "");
s.put(SETTINGS_KEY_ATTEMPTS, "");
s.put(SETTINGS_KEY_LAST_ATTEMPT, "");
s.put(SETTINGS_KEY_LAST_SUCCESS, "");
s.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) { for (MailboxHook hook : hooks) {
hook.mailboxUnpaired(txn); hook.mailboxUnpaired(txn);
@@ -116,39 +112,42 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
serverSupports); serverSupports);
} }
@Override
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
recordSuccessfulConnection(txn, now, null);
}
@Override @Override
public void recordSuccessfulConnection(Transaction txn, long now, public void recordSuccessfulConnection(Transaction txn, long now,
List<MailboxVersion> versions) throws DbException { @Nullable List<MailboxVersion> versions) 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;
Settings s = new Settings(); Settings s = new Settings();
// record the successful connection // fetch version that the server supports first
List<MailboxVersion> serverSupports;
if (versions == null) {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
serverSupports = parseServerSupports(oldSettings);
} else {
serverSupports = versions;
// store new versions
encodeServerSupports(serverSupports, s);
}
// now record the successful connection
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now); s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now); s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0); s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
encodeServerSupports(versions, s);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE); settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.serverSupportedVersionsReceived(txn, versions);
}
// broadcast status event // broadcast status event
MailboxStatus status = new MailboxStatus(now, now, 0, versions); MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
txn.attach(new OwnMailboxConnectionStatusEvent(status)); txn.attach(new OwnMailboxConnectionStatusEvent(status));
} }
@Override @Override
public void recordFailedConnectionAttempt(Transaction txn, long now) public void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException { throws DbException {
// if we no longer have a paired mailbox, return
Settings oldSettings = Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE); settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = oldSettings.get(SETTINGS_KEY_ONION);
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0); int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0); long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
Settings newSettings = new Settings(); Settings newSettings = new Settings();

View File

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

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable; 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.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -13,8 +12,6 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId; import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.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.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord; import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent; import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
@@ -35,7 +32,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -62,16 +58,9 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
* <p> * <p>
* If there's no data to send, the worker listens for events indicating * If there's no data to send, the worker listens for events indicating
* that new data may be ready to send. * 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 { private enum State {
CREATED, CREATED,
CONNECTED_TO_CONTACT,
CHECKING_FOR_DATA, CHECKING_FOR_DATA,
WAITING_FOR_DATA, WAITING_FOR_DATA,
CONNECTIVITY_CHECK, CONNECTIVITY_CHECK,
@@ -106,7 +95,6 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
private final Clock clock; private final Clock clock;
private final TaskScheduler taskScheduler; private final TaskScheduler taskScheduler;
private final EventBus eventBus; private final EventBus eventBus;
private final ConnectionRegistry connectionRegistry;
private final ConnectivityChecker connectivityChecker; private final ConnectivityChecker connectivityChecker;
private final MailboxApiCaller mailboxApiCaller; private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi; private final MailboxApi mailboxApi;
@@ -133,7 +121,6 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
Clock clock, Clock clock,
TaskScheduler taskScheduler, TaskScheduler taskScheduler,
EventBus eventBus, EventBus eventBus,
ConnectionRegistry connectionRegistry,
ConnectivityChecker connectivityChecker, ConnectivityChecker connectivityChecker,
MailboxApiCaller mailboxApiCaller, MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi, MailboxApi mailboxApi,
@@ -146,7 +133,6 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
this.clock = clock; this.clock = clock;
this.taskScheduler = taskScheduler; this.taskScheduler = taskScheduler;
this.eventBus = eventBus; this.eventBus = eventBus;
this.connectionRegistry = connectionRegistry;
this.connectivityChecker = connectivityChecker; this.connectivityChecker = connectivityChecker;
this.mailboxApiCaller = mailboxApiCaller; this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi; this.mailboxApi = mailboxApi;
@@ -196,12 +182,6 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
synchronized (lock) { synchronized (lock) {
checkTask = null; checkTask = null;
if (state != State.CHECKING_FOR_DATA) return; 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"); LOG.info("Checking for data to send");
try { try {
@@ -348,13 +328,13 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
LOG.info("Uploading file"); LOG.info("Uploading file");
mailboxApi.addFile(mailboxProperties, folderId, file); mailboxApi.addFile(mailboxProperties, folderId, file);
markMessagesSentOrAcked(sessionRecord); markMessagesSentOrAcked(sessionRecord);
delete(file);
synchronized (lock) { synchronized (lock) {
if (state != State.WRITING_UPLOADING) return; if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA; state = State.CHECKING_FOR_DATA;
apiCall = null; apiCall = null;
this.file = null; this.file = null;
} }
delete(file);
checkForDataToSend(); checkForDataToSend();
} }
@@ -384,14 +364,8 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
onDataToSend(); onDataToSend();
} }
} else if (e instanceof MessageSharedEvent) { } else if (e instanceof MessageSharedEvent) {
MessageSharedEvent m = (MessageSharedEvent) e; LOG.info("Message shared");
// If the contact is present in the map (ie the value is not null) onDataToSend();
// 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) { } else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e; GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED && if (g.getVisibility() == SHARED &&
@@ -399,18 +373,6 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
LOG.info("Group shared"); LOG.info("Group shared");
onDataToSend(); 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();
}
} }
} }
@@ -429,36 +391,4 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
// If we had scheduled a wakeup when data was due to be sent, cancel it // If we had scheduled a wakeup when data was due to be sent, cancel it
if (wakeupTask != null) wakeupTask.cancel(); if (wakeupTask != null) wakeupTask.cancel();
} }
@EventExecutor
private void onContactConnected() {
Cancellable wakeupTask = null, checkTask = null;
synchronized (lock) {
if (state == State.DESTROYED) return;
// If we're checking for data to send, waiting for data to send,
// or checking connectivity then wait until we disconnect from
// the contact before proceeding. If we're writing or uploading
// a file then continue
if (state == State.CHECKING_FOR_DATA ||
state == State.WAITING_FOR_DATA ||
state == State.CONNECTIVITY_CHECK) {
state = State.CONNECTED_TO_CONTACT;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
checkTask = this.checkTask;
this.checkTask = null;
}
}
if (wakeupTask != null) wakeupTask.cancel();
if (checkTask != null) checkTask.cancel();
}
@EventExecutor
private void onContactDisconnected() {
synchronized (lock) {
if (state != State.CONNECTED_TO_CONTACT) return;
state = State.CHECKING_FOR_DATA;
}
ioExecutor.execute(this::checkForDataToSend);
}
} }

View File

@@ -24,8 +24,4 @@ interface MailboxWorkerFactory {
ConnectivityChecker connectivityChecker, ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor, TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties); MailboxProperties properties);
MailboxWorker createContactListWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
MailboxProperties properties);
} }

View File

@@ -1,13 +1,11 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId; import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
@@ -26,11 +24,9 @@ class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
private final Clock clock; private final Clock clock;
private final TaskScheduler taskScheduler; private final TaskScheduler taskScheduler;
private final EventBus eventBus; private final EventBus eventBus;
private final ConnectionRegistry connectionRegistry;
private final MailboxApiCaller mailboxApiCaller; private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi; private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager; private final MailboxFileManager mailboxFileManager;
private final MailboxUpdateManager mailboxUpdateManager;
@Inject @Inject
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor, MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
@@ -38,21 +34,17 @@ class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
Clock clock, Clock clock,
TaskScheduler taskScheduler, TaskScheduler taskScheduler,
EventBus eventBus, EventBus eventBus,
ConnectionRegistry connectionRegistry,
MailboxApiCaller mailboxApiCaller, MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi, MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager, MailboxFileManager mailboxFileManager) {
MailboxUpdateManager mailboxUpdateManager) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.db = db; this.db = db;
this.clock = clock; this.clock = clock;
this.taskScheduler = taskScheduler; this.taskScheduler = taskScheduler;
this.eventBus = eventBus; this.eventBus = eventBus;
this.connectionRegistry = connectionRegistry;
this.mailboxApiCaller = mailboxApiCaller; this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi; this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager; this.mailboxFileManager = mailboxFileManager;
this.mailboxUpdateManager = mailboxUpdateManager;
} }
@Override @Override
@@ -61,9 +53,9 @@ class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
MailboxProperties properties, MailboxFolderId folderId, MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId) { ContactId contactId) {
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db, MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
clock, taskScheduler, eventBus, connectionRegistry, clock, taskScheduler, eventBus, connectivityChecker,
connectivityChecker, mailboxApiCaller, mailboxApi, mailboxApiCaller, mailboxApi, mailboxFileManager,
mailboxFileManager, properties, folderId, contactId); properties, folderId, contactId);
eventBus.addListener(worker); eventBus.addListener(worker);
return worker; return worker;
} }
@@ -83,19 +75,7 @@ class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
ConnectivityChecker connectivityChecker, ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor, TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) { MailboxProperties properties) {
return new OwnMailboxDownloadWorker(connectivityChecker, // TODO
reachabilityMonitor, mailboxApiCaller, mailboxApi, throw new UnsupportedOperationException();
mailboxFileManager, properties);
}
@Override
public MailboxWorker createContactListWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
MailboxProperties properties) {
OwnMailboxContactListWorker worker = new OwnMailboxContactListWorker(
ioExecutor, db, eventBus, connectivityChecker, mailboxApiCaller,
mailboxApi, mailboxUpdateManager, properties);
eventBus.addListener(worker);
return worker;
} }
} }

View File

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

View File

@@ -14,7 +14,6 @@ import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -31,7 +30,6 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private final TransactionManager db; private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
@Inject
OwnMailboxConnectivityChecker(Clock clock, OwnMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller, MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi, MailboxApi mailboxApi,
@@ -59,7 +57,6 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private boolean checkConnectivityAndStoreResult( private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException { MailboxProperties properties) throws DbException {
try { try {
LOG.info("Checking whether own mailbox is reachable");
List<MailboxVersion> serverSupports = List<MailboxVersion> serverSupports =
mailboxApi.getServerSupports(properties); mailboxApi.getServerSupports(properties);
LOG.info("Own mailbox is reachable"); LOG.info("Own mailbox is reachable");

View File

@@ -303,7 +303,7 @@ class OwnMailboxContactListWorker
mailboxApi.deleteContact(mailboxProperties, c); mailboxApi.deleteContact(mailboxProperties, c);
} catch (TolerableFailureException e) { } catch (TolerableFailureException e) {
// Catch this so we can continue to the next update // Catch this so we can continue to the next update
LOG.warning("Contact does not exist"); logException(LOG, INFO, e);
} }
updateContactList(); updateContactList();
} }

View File

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

View File

@@ -12,8 +12,7 @@ public interface CircumventionProvider {
DEFAULT_OBFS4, DEFAULT_OBFS4,
NON_DEFAULT_OBFS4, NON_DEFAULT_OBFS4,
VANILLA, VANILLA,
MEEK, MEEK
SNOWFLAKE
} }
/** /**
@@ -42,14 +41,13 @@ public interface CircumventionProvider {
* Countries where non-default obfs4 or vanilla bridges are likely to work. * Countries where non-default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}. * 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 * Countries where vanilla bridges are blocked via DPI but non-default
* obfs4 bridges, meek and snowflake may work. Should be a subset of * obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}.
* {@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. * Returns true if vanilla Tor connections are blocked in the given country.
@@ -70,6 +68,6 @@ public interface CircumventionProvider {
List<BridgeType> getSuitableBridgeTypes(String countryCode); List<BridgeType> getSuitableBridgeTypes(String countryCode);
@IoExecutor @IoExecutor
List<String> getBridges(BridgeType type, String countryCode, List<String> getBridges(BridgeType type);
boolean letsEncrypt);
} }

View File

@@ -7,10 +7,8 @@ import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; 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.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4; import static org.briarproject.bramble.plugin.tor.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; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
@Immutable @Immutable
@@ -28,8 +25,6 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeTy
class CircumventionProviderImpl implements CircumventionProvider { class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges"; 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 = private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(asList(BLOCKED)); new HashSet<>(asList(BLOCKED));
@@ -63,7 +58,7 @@ class CircumventionProviderImpl implements CircumventionProvider {
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) { } else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA); return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) { } else if (DPI_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE); return asList(NON_DEFAULT_OBFS4, MEEK);
} else { } else {
return asList(DEFAULT_OBFS4, VANILLA); return asList(DEFAULT_OBFS4, VANILLA);
} }
@@ -71,8 +66,7 @@ class CircumventionProviderImpl implements CircumventionProvider {
@Override @Override
@IoExecutor @IoExecutor
public List<String> getBridges(BridgeType type, String countryCode, public List<String> getBridges(BridgeType type) {
boolean letsEncrypt) {
InputStream is = requireNonNull(getClass().getClassLoader() InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME)); .getResourceAsStream(BRIDGE_FILE_NAME));
Scanner scanner = new Scanner(is); Scanner scanner = new Scanner(is);
@@ -85,45 +79,10 @@ class CircumventionProviderImpl implements CircumventionProvider {
(type == VANILLA && line.startsWith("v ")) || (type == VANILLA && line.startsWith("v ")) ||
(type == MEEK && line.startsWith("m "))) { (type == MEEK && line.startsWith("m "))) {
bridges.add(line.substring(2)); 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(); scanner.close();
return bridges; 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");
}
} }

View File

@@ -65,7 +65,6 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -94,8 +93,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.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.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose; import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -213,10 +213,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return new File(torDirectory, "obfs4proxy"); return new File(torDirectory, "obfs4proxy");
} }
protected File getSnowflakeExecutableFile() {
return new File(torDirectory, "snowflake");
}
@Override @Override
public TransportId getId() { public TransportId getId() {
return TorConstants.ID; return TorConstants.ID;
@@ -243,14 +239,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
// Load the settings // Load the settings
settings = callback.getSettings(); settings = callback.getSettings();
try { // Install or update the assets if necessary
// Install or update the assets if necessary if (!assetsAreUpToDate()) installAssets();
if (!assetsAreUpToDate()) installAssets();
// Start from the default config every time
extract(getConfigInputStream(), configFile);
} catch (IOException e) {
throw new PluginException(e);
}
if (cookieFile.exists() && !cookieFile.delete()) if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted"); LOG.warning("Old auth cookie not deleted");
// Start a new Tor process // Start a new Tor process
@@ -312,7 +302,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
info = controlConnection.getInfo("status/circuit-established"); info = controlConnection.getInfo("status/circuit-established");
if ("1".equals(info)) { if ("1".equals(info)) {
LOG.info("Tor has already built a circuit"); LOG.info("Tor has already built a circuit");
state.setCircuitBuilt(true); state.getAndSetCircuitBuilt(true);
} }
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -331,21 +321,25 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return doneFile.lastModified() > getLastUpdateTime(); return doneFile.lastModified() > getLastUpdateTime();
} }
private void installAssets() throws IOException { private void installAssets() throws PluginException {
// The done file may already exist from a previous installation try {
//noinspection ResultOfMethodCallIgnored // The done file may already exist from a previous installation
doneFile.delete(); //noinspection ResultOfMethodCallIgnored
// The GeoIP file may exist from a previous installation - we can doneFile.delete();
// save some space by deleting it. // The GeoIP file may exist from a previous installation - we can
// TODO: Remove after a reasonable migration period // save some space by deleting it.
// (added 2022-03-29) // TODO: Remove after a reasonable migration period
//noinspection ResultOfMethodCallIgnored // (added 2022-03-29)
geoIpFile.delete(); //noinspection ResultOfMethodCallIgnored
installTorExecutable(); geoIpFile.delete();
installObfs4Executable(); installTorExecutable();
installSnowflakeExecutable(); installObfs4Executable();
if (!doneFile.createNewFile()) extract(getConfigInputStream(), configFile);
LOG.warning("Failed to create done file"); if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
} catch (IOException e) {
throw new PluginException(e);
}
} }
protected void extract(InputStream in, File dest) throws IOException { protected void extract(InputStream in, File dest) throws IOException {
@@ -369,29 +363,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!obfs4File.setExecutable(true, true)) throw new IOException(); 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 { 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 { 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 InputStream in = resourceProvider
.getResourceInputStream(basename + "_" + architecture, ".zip"); .getResourceInputStream("obfs4proxy_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in); ZipInputStream zin = new ZipInputStream(in);
if (zin.getNextEntry() == null) throw new IOException(); if (zin.getNextEntry() == null) throw new IOException();
return zin; return zin;
@@ -416,12 +398,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "SocksPort", torSocksPort); append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n"); strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n"); strb.append("GeoIPv6File\n");
append(strb, "ConnectionPadding", 0);
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
//noinspection CharsetObjectCanBeUsed //noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream( return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8"))); strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -571,7 +547,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!state.enableNetwork(enable)) return; // Unchanged state.enableNetwork(enable);
try { try {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1"); controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -579,37 +555,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 { throws IOException {
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
try { try {
if (bridgeTypes.isEmpty()) { if (enable) {
controlConnection.setConf("UseBridges", "0");
controlConnection.resetConf(singletonList("Bridge"));
} else {
Collection<String> conf = new ArrayList<>(); Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1"); conf.add("UseBridges 1");
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) { for (BridgeType bridgeType : bridgeTypes) {
conf.addAll(circumventionProvider conf.addAll(circumventionProvider.getBridges(bridgeType));
.getBridges(bridgeType, countryCode, letsEncrypt));
} }
controlConnection.setConf(conf); controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
} }
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
/**
* 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 @Override
public void stop() { public void stop() {
ServerSocket ss = state.setStopped(); ServerSocket ss = state.setStopped();
@@ -617,6 +590,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (controlSocket != null && controlConnection != null) { if (controlSocket != null && controlConnection != null) {
try { try {
LOG.info("Stopping Tor"); LOG.info("Stopping Tor");
controlConnection.setConf("DisableNetwork", "1");
controlConnection.shutdownTor("TERM"); controlConnection.shutdownTor("TERM");
controlSocket.close(); controlSocket.close();
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -784,7 +758,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void circuitStatus(String status, String id, String path) { public void circuitStatus(String status, String id, String path) {
// In case of races between receiving CIRCUIT_ESTABLISHED and setting // In case of races between receiving CIRCUIT_ESTABLISHED and setting
// DisableNetwork, set our circuitBuilt flag if not already set // DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && state.setCircuitBuilt(true)) { if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built"); LOG.info("Circuit built");
backoff.reset(); backoff.reset();
} }
@@ -831,13 +805,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
@Override
public void controlConnectionClosed() {
if (state.isTorRunning()) {
throw new RuntimeException("Control connection closed");
}
}
private String removeSeverity(String msg) { private String removeSeverity(String msg) {
return msg.replaceFirst("[^ ]+ ", ""); return msg.replaceFirst("[^ ]+ ", "");
} }
@@ -848,12 +815,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
state.setBootstrapped(); state.setBootstrapped();
backoff.reset(); backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (state.setCircuitBuilt(true)) { if (!state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built"); LOG.info("Circuit built");
backoff.reset(); backoff.reset();
} }
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) { } else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.setCircuitBuilt(false)) { if (state.getAndSetCircuitBuilt(false)) {
LOG.info("Circuit not built"); LOG.info("Circuit not built");
// TODO: Disable and re-enable network to prompt Tor to rebuild // TODO: Disable and re-enable network to prompt Tor to rebuild
// its guard/bridge connections? This will also close any // its guard/bridge connections? This will also close any
@@ -944,8 +911,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
int reasonsDisabled = 0; int reasonsDisabled = 0;
boolean enableNetwork = false, enableConnectionPadding = false; boolean enableNetwork = false, enableBridges = false;
List<BridgeType> bridgeTypes = emptyList(); boolean enableConnectionPadding = false;
List<BridgeType> bridgeTypes =
circumventionProvider.getSuitableBridgeTypes(country);
if (!online) { if (!online) {
LOG.info("Disabling network, device is offline"); LOG.info("Disabling network, device is offline");
@@ -974,12 +943,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true; enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES || if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (ipv6Only) { if (ipv6Only) bridgeTypes = singletonList(MEEK);
bridgeTypes = asList(MEEK, SNOWFLAKE); enableBridges = true;
} else {
bridgeTypes = circumventionProvider
.getSuitableBridgeTypes(country);
}
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Using bridge types " + bridgeTypes); LOG.info("Using bridge types " + bridgeTypes);
} }
@@ -999,9 +964,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try { try {
if (enableNetwork) { if (enableNetwork) {
enableBridges(bridgeTypes, country); enableBridges(enableBridges, bridgeTypes);
enableConnectionPadding(enableConnectionPadding); enableConnectionPadding(enableConnectionPadding);
enableIpv6(ipv6Only); useIpv6(ipv6Only);
} }
enableNetwork(enableNetwork); enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
@@ -1011,7 +976,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void enableConnectionPadding(boolean enable) throws IOException { private void enableConnectionPadding(boolean enable) throws IOException {
if (!state.enableConnectionPadding(enable)) return; // Unchanged
try { try {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
@@ -1019,11 +983,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
private void enableIpv6(boolean enable) throws IOException { private void useIpv6(boolean ipv6Only) throws IOException {
if (!state.enableIpv6(enable)) return; // Unchanged
try { try {
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1"); controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0"); controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
} catch (TorNotRunningException e) { } catch (TorNotRunningException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -1038,8 +1001,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
stopped = false, stopped = false,
networkInitialised = false, networkInitialised = false,
networkEnabled = false, networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
bootstrapped = false, bootstrapped = false,
circuitBuilt = false, circuitBuilt = false,
settingsChecked = false; settingsChecked = false;
@@ -1054,9 +1015,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this") @GuardedBy("this")
private int orConnectionsConnected = 0; private int orConnectionsConnected = 0;
@GuardedBy("this")
private List<BridgeType> bridgeTypes = emptyList();
private synchronized void setStarted() { private synchronized void setStarted() {
started = true; started = true;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
@@ -1077,66 +1035,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private synchronized void setBootstrapped() { private synchronized void setBootstrapped() {
boolean wasBootstrapped = bootstrapped;
bootstrapped = true; bootstrapped = true;
if (!wasBootstrapped) callback.pluginStateChanged(getState());
}
/**
* Sets the `circuitBuilt` flag and returns true if the flag has
* changed.
*/
private synchronized boolean setCircuitBuilt(boolean built) {
if (built == circuitBuilt) return false; // Unchanged
circuitBuilt = built;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
return true; // Changed
} }
/** private synchronized boolean getAndSetCircuitBuilt(boolean built) {
* Sets the `networkEnabled` flag and returns true if the flag has boolean old = circuitBuilt;
* changed. circuitBuilt = built;
*/ if (built != old) callback.pluginStateChanged(getState());
private synchronized boolean enableNetwork(boolean enable) { return old;
boolean wasInitialised = networkInitialised; }
boolean wasEnabled = networkEnabled;
private synchronized void enableNetwork(boolean enable) {
networkInitialised = true; networkInitialised = true;
networkEnabled = enable; networkEnabled = enable;
if (!enable) circuitBuilt = false; if (!enable) circuitBuilt = false;
if (!wasInitialised || enable != wasEnabled) { callback.pluginStateChanged(getState());
callback.pluginStateChanged(getState());
}
return enable != wasEnabled;
} }
/** private synchronized void setReasonsDisabled(int reasonsDisabled) {
* Sets the `paddingEnabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableConnectionPadding(boolean enable) {
if (enable == paddingEnabled) return false; // Unchanged
paddingEnabled = enable;
return true; // Changed
}
/**
* Sets the `ipv6Enabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableIpv6(boolean enable) {
if (enable == ipv6Enabled) return false; // Unchanged
ipv6Enabled = enable;
return true; // Changed
}
private synchronized void setReasonsDisabled(int reasons) {
boolean wasChecked = settingsChecked;
settingsChecked = true; settingsChecked = true;
int oldReasons = reasonsDisabled; this.reasonsDisabled = reasonsDisabled;
reasonsDisabled = reasons; callback.pluginStateChanged(getState());
if (!wasChecked || reasons != oldReasons) {
callback.pluginStateChanged(getState());
}
} }
// Doesn't affect getState() // Doesn't affect getState()
@@ -1151,17 +1071,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (serverSocket == ss) serverSocket = null; if (serverSocket == ss) serverSocket = null;
} }
/**
* Sets the list of bridge types being used and returns true if the
* list has changed. The list is empty if bridges are disabled.
* Doesn't affect getState().
*/
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
if (types.equals(bridgeTypes)) return false; // Unchanged
bridgeTypes = types;
return true; // Changed
}
private synchronized State getState() { private synchronized State getState() {
if (!started || stopped || !settingsChecked) { if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING; return STARTING_STOPPING;

View File

@@ -44,7 +44,6 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -234,13 +233,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof MessageSharedEvent) { } else if (e instanceof MessageSharedEvent) {
MessageSharedEvent m = (MessageSharedEvent) e; generateOffer();
// If the contact is present in the map (ie the value is not null)
// and the value is true, the message's group is shared with the
// contact and therefore the message may now be sendable
if (m.getGroupVisibility().get(contactId) == TRUE) {
generateOffer();
}
} else if (e instanceof GroupVisibilityUpdatedEvent) { } else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e; GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED && if (g.getVisibility() == SHARED &&

View File

@@ -14,9 +14,7 @@ import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -63,32 +61,26 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
@Override @Override
void sendAcks() throws DbException, IOException { void sendAcks() throws DbException, IOException {
List<MessageId> idsToAck = loadMessageIdsToAck(); while (!isInterrupted()) {
int idsSent = 0; Collection<MessageId> idsToAck = loadMessageIdsToAck();
while (idsSent < idsToAck.size() && !isInterrupted()) { if (idsToAck.isEmpty()) break;
int idsRemaining = idsToAck.size() - idsSent; recordWriter.writeAck(new Ack(idsToAck));
long capacity = getRemainingCapacity(); sessionRecord.onAckSent(idsToAck);
long idCapacity =
(capacity - RECORD_HEADER_BYTES) / MessageId.LENGTH;
if (idCapacity == 0) break; // Out of capacity
int idsInRecord = (int) min(idCapacity, MAX_MESSAGE_IDS);
int idsToSend = min(idsRemaining, idsInRecord);
List<MessageId> acked =
idsToAck.subList(idsSent, idsSent + idsToSend);
recordWriter.writeAck(new Ack(acked));
sessionRecord.onAckSent(acked);
LOG.info("Sent ack"); LOG.info("Sent ack");
idsSent += idsToSend;
} }
} }
private List<MessageId> loadMessageIdsToAck() throws DbException { private Collection<MessageId> loadMessageIdsToAck() throws DbException {
long idCapacity = (getRemainingCapacity() - RECORD_HEADER_BYTES)
/ MessageId.LENGTH;
if (idCapacity <= 0) return emptyList(); // Out of capacity
int maxMessageIds = (int) min(idCapacity, MAX_MESSAGE_IDS);
Collection<MessageId> ids = db.transactionWithResult(true, txn -> Collection<MessageId> ids = db.transactionWithResult(true, txn ->
db.getMessagesToAck(txn, contactId)); db.getMessagesToAck(txn, contactId, maxMessageIds));
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(ids.size() + " messages to ack"); LOG.info(ids.size() + " messages to ack");
} }
return new ArrayList<>(ids); return ids;
} }
private long getRemainingCapacity() { private long getRemainingCapacity() {

View File

@@ -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 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 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 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 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0 n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0 n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 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 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0 n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
n Bridge obfs4 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 202.61.224.111:6902 A4F91299763DB925AE3BD29A0FC1A9821E5D9BAE cert=NBKm2MJ83wMvYShkqpD5RrbDtW5YpIZrFNnMw7Dj1XOM3plU60Bh4eziaQXe8fGtb8ZqKg iat-mode=0
n Bridge obfs4 87.121.72.109:9002 C8081D4731C953FA4AE166946E72B29153351E34 cert=bikAqxKV6Ch5gFCBTdPI28VeShYa1ZgkLmvc7YZNLWFsFZoaCULL/3AQKjpIfvSiJs5jGQ iat-mode=0
n Bridge obfs4 172.104.17.96:17900 B6B37AC96E163D0A5ECE55826D17B50B70F0A7F8 cert=gUz7svhPxoALgeN4lMYrXK7NBnaDqwu6SKRJOhaO9IIMBpnB8UhMCMKzzMho3b0RxWzBVg iat-mode=0
n Bridge obfs4 70.34.249.113:443 F441B16ABB1055794C2CE01821FC05047B2C8CFC cert=MauLNoyq8EwjY4Qe0oASYzs2hXdSjNgy+BtP9oo1naHhRsyKTtAZzeNv08RnzWjMJrTwcg iat-mode=0
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31 v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
v Bridge 185.189.195.124:8199 A1F3EE78F9C2343668E68FEB84358A4C742831A5 m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
v Bridge 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

View File

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

View File

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

View File

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

View File

@@ -168,7 +168,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertTrue(db.containsGroup(txn, groupId)); assertTrue(db.containsGroup(txn, groupId));
assertTrue(db.containsMessage(txn, messageId)); assertTrue(db.containsMessage(txn, messageId));
assertEquals(groupId, db.getGroupId(txn, messageId));
assertArrayEquals(message.getBody(), assertArrayEquals(message.getBody(),
db.getMessage(txn, messageId).getBody()); db.getMessage(txn, messageId).getBody());

View File

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

View File

@@ -40,8 +40,6 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
@Test @Test
public void testStartAndDestroyWithNoContactsAssigned() { public void testStartAndDestroyWithNoContactsAssigned() {
client.start(); client.start();
expectDestroyConnectivityChecker();
client.destroy(); client.destroy();
} }
@@ -56,7 +54,6 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
// When the client is destroyed, the worker should be destroyed // When the client is destroyed, the worker should be destroyed
expectDestroyWorker(uploadWorker); expectDestroyWorker(uploadWorker);
expectDestroyConnectivityChecker();
client.destroy(); client.destroy();
} }
@@ -74,12 +71,11 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
client.deassignContactForUpload(contactId); client.deassignContactForUpload(contactId);
context.assertIsSatisfied(); context.assertIsSatisfied();
expectDestroyConnectivityChecker();
client.destroy(); client.destroy();
} }
@Test @Test
public void testAssignContactForDownloadAndDestroyClient() { public void assignContactForDownloadAndDestroyClient() {
client.start(); client.start();
// When the contact is assigned, the worker should be created and // When the contact is assigned, the worker should be created and
@@ -89,12 +85,11 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
// When the client is destroyed, the worker should be destroyed // When the client is destroyed, the worker should be destroyed
expectDestroyWorker(downloadWorker); expectDestroyWorker(downloadWorker);
expectDestroyConnectivityChecker();
client.destroy(); client.destroy();
} }
@Test @Test
public void testAssignAndDeassignContactForDownload() { public void assignAndDeassignContactForDownload() {
client.start(); client.start();
// When the contact is assigned, the worker should be created and // When the contact is assigned, the worker should be created and
@@ -107,7 +102,6 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
client.deassignContactForDownload(contactId); client.deassignContactForDownload(contactId);
context.assertIsSatisfied(); context.assertIsSatisfied();
expectDestroyConnectivityChecker();
client.destroy(); client.destroy();
} }
@@ -134,10 +128,4 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
oneOf(worker).destroy(); oneOf(worker).destroy();
}}); }});
} }
private void expectDestroyConnectivityChecker() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).destroy();
}});
}
} }

View File

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

View File

@@ -630,7 +630,6 @@ public class MailboxApiTest extends BrambleTestCase {
server.enqueue(new MockResponse().setBody(invalidResponse3)); server.enqueue(new MockResponse().setBody(invalidResponse3));
server.enqueue(new MockResponse().setBody(invalidResponse4)); server.enqueue(new MockResponse().setBody(invalidResponse4));
server.enqueue(new MockResponse().setResponseCode(401)); server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(404));
server.enqueue(new MockResponse().setResponseCode(500)); server.enqueue(new MockResponse().setResponseCode(500));
server.start(); server.start();
String baseUrl = getBaseUrl(server); String baseUrl = getBaseUrl(server);
@@ -707,21 +706,13 @@ public class MailboxApiTest extends BrambleTestCase {
assertEquals("GET", request8.getMethod()); assertEquals("GET", request8.getMethod());
assertToken(request8, token); assertToken(request8, token);
// 404 not found // 500 internal server error
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class,
api.getFiles(properties, contactInboxId)); () -> api.getFiles(properties, contactInboxId));
RecordedRequest request9 = server.takeRequest(); RecordedRequest request9 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request9.getPath()); assertEquals("/files/" + contactInboxId, request9.getPath());
assertEquals("GET", request9.getMethod()); assertEquals("GET", request9.getMethod());
assertToken(request9, token); assertToken(request9, token);
// 500 internal server error
assertThrows(ApiException.class,
() -> api.getFiles(properties, contactInboxId));
RecordedRequest request10 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request10.getPath());
assertEquals("GET", request10.getMethod());
assertToken(request10, token);
} }
@Test @Test

View File

@@ -1,929 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.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.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
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.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
public class MailboxClientManagerTest extends BrambleMockTestCase {
private final Executor eventExecutor =
context.mock(Executor.class, "eventExecutor");
private final Executor dbExecutor =
context.mock(Executor.class, "dbExecutor");
private final TransactionManager db =
context.mock(TransactionManager.class);
private final ContactManager contactManager =
context.mock(ContactManager.class);
private final PluginManager pluginManager =
context.mock(PluginManager.class);
private final MailboxSettingsManager mailboxSettingsManager =
context.mock(MailboxSettingsManager.class);
private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class);
private final MailboxClientFactory mailboxClientFactory =
context.mock(MailboxClientFactory.class);
private final TorReachabilityMonitor reachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
private final MailboxClient ownClient =
context.mock(MailboxClient.class, "ownClient");
private final MailboxClient contactClient =
context.mock(MailboxClient.class, "contactClient");
private final MailboxClientManager manager =
new MailboxClientManager(eventExecutor, dbExecutor, db,
contactManager, pluginManager, mailboxSettingsManager,
mailboxUpdateManager, mailboxClientFactory,
reachabilityMonitor);
private final Contact contact = getContact();
private final List<MailboxVersion> incompatibleVersions =
singletonList(new MailboxVersion(999, 0));
private final MailboxProperties ownProperties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final MailboxProperties localProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxProperties remoteProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxProperties remotePropertiesForNewMailbox =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxUpdate localUpdateWithoutMailbox =
new MailboxUpdate(CLIENT_SUPPORTS);
private final MailboxUpdate remoteUpdateWithoutMailbox =
new MailboxUpdate(CLIENT_SUPPORTS);
private final MailboxUpdate remoteUpdateWithIncompatibleClientVersions =
new MailboxUpdate(incompatibleVersions);
private final MailboxUpdateWithMailbox localUpdateWithMailbox =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, localProperties);
private final MailboxUpdateWithMailbox remoteUpdateWithMailbox =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, remoteProperties);
private final MailboxUpdateWithMailbox remoteUpdateWithNewMailbox =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS,
remotePropertiesForNewMailbox);
@Test
public void testLoadsMailboxUpdatesAtStartupWhenOffline() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're offline so there's nothing
// else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ENABLING);
manager.startService();
// At shutdown there should be no clients to destroy. The manager
// should destroy the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testLoadsMailboxUpdatesAtStartupWhenOnline() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're online but we don't have a
// mailbox and neither does the contact, so there's nothing else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// At shutdown there should be no clients to destroy. The manager
// should destroy the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToOurMailboxIfContactHasNoMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't.
//
// We're online, so the manager should create a client for our own
// mailbox and assign the contact to it for upload and download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
expectAssignContactToOwnMailboxForUpload();
manager.startService();
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactToOurMailboxIfContactHasNotSentUpdate()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// has never sent us an update, so the remote update is null.
//
// We're online, so the manager should create a client for our own
// mailbox. We don't know what API versions the contact supports,
// if any, so the contact should not be assigned to our mailbox.
expectLoadUpdates(localUpdateWithMailbox, null, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
manager.startService();
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactToOurMailboxIfContactHasIncompatibleClientVersions()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't. The contact's client API versions are incompatible with
// our mailbox.
//
// We're online, so the manager should create a client for our own
// mailbox. The contact's client API versions are incompatible with
// our mailbox, so the contact should not be assigned to our mailbox.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithIncompatibleClientVersions, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
manager.startService();
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToContactMailboxIfWeHaveNoMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox but the
// contact does.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to it for upload and
// download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForDownload(remoteProperties);
expectAssignContactToContactMailboxForUpload(remoteProperties);
manager.startService();
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToBothMailboxesIfWeBothHaveMailboxes()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testCreatesClientsWhenTorBecomesActive() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't. We're offline so there's nothing else to do.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ENABLING);
manager.startService();
// When we come online, the manager should create a client for our own
// mailbox and assign the contact to it for upload and download.
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
expectAssignContactToOwnMailboxForUpload();
manager.eventOccurred(new TransportActiveEvent(ID));
// When we go offline, the manager should destroy the client for our
// own mailbox.
expectDestroyClientForOwnMailbox();
manager.eventOccurred(new TransportInactiveEvent(ID));
// At shutdown the manager should destroy the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToOurMailboxWhenPaired() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're online but we don't have a
// mailbox and neither does the contact, so there's nothing else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox and assign the contact to our mailbox for upload and
// download.
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactToOurMailboxWhenPaired() throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. The contact has a mailbox but we
// don't.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to it for upload and
// download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForDownload(remoteProperties);
expectAssignContactToContactMailboxForUpload(remoteProperties);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox and reassign the contact to our mailbox for download.
expectCreateClientForOwnMailbox();
expectDeassignContactFromContactMailboxForDownload();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactWhenPairedIfContactHasNotSentUpdate()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox and the
// contact has never sent us an update, so the remote update is null.
expectLoadUpdates(localUpdateWithoutMailbox, null, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox. We don't know whether the contact can use our mailbox, so
// the contact should not be assigned to our mailbox.
expectCreateClientForOwnMailbox();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotAssignContactWhenPairedIfContactHasIncompatibleClientVersions()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox and neither
// does the contact. The contact's client API versions are
// incompatible with our mailbox.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithIncompatibleClientVersions, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When we pair a mailbox, the manager should create a client for our
// mailbox. The contact's client API versions are incompatible with
// our mailbox, so the contact should not be assigned to our mailbox.
expectCreateClientForOwnMailbox();
manager.eventOccurred(new MailboxPairedEvent(ownProperties,
singletonMap(contact.getId(), localUpdateWithMailbox)));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactToContactMailboxWhenUnpaired()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When we unpair our mailbox, the manager should destroy the client
// for our mailbox and reassign the contact to the contact's mailbox
// for download.
expectDestroyClientForOwnMailbox();
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.eventOccurred(new MailboxUnpairedEvent(
singletonMap(contact.getId(), localUpdateWithoutMailbox)));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDeassignsContactForUploadAndDownloadWhenContactRemoved()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't.
//
// We're online, so the manager should create a client for our mailbox.
// The manager should assign the contact to our mailbox for upload and
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact is removed, the manager should deassign the contact
// from our mailbox for upload and download.
expectDeassignContactFromOwnMailboxForUpload();
expectDeassignContactFromOwnMailboxForDownload();
manager.eventOccurred(new ContactRemovedEvent(contact.getId()));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDeassignsContactForDownloadWhenContactRemoved()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact is removed, the manager should destroy the client
// for the contact's mailbox and deassign the contact from our mailbox
// for download.
expectDestroyClientForContactMailbox();
expectDeassignContactFromOwnMailboxForDownload();
manager.eventOccurred(new ContactRemovedEvent(contact.getId()));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToContactMailboxWhenContactPairsMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We're online but we don't have a
// mailbox and neither does the contact, so there's nothing else to do.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithoutMailbox, null);
expectCheckPluginState(ACTIVE);
manager.startService();
// When the contact pairs a mailbox, the manager should create a client
// for the contact's mailbox and assign the contact to the contact's
// mailbox for upload and download.
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactForUploadWhenContactPairsMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't.
//
// We're online, so the manager should create a client for our mailbox.
// The manager should assign the contact to our mailbox for upload and
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithoutMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact pairs a mailbox, the manager should create a client
// for the contact's mailbox and reassign the contact to the contact's
// mailbox for upload.
expectCreateClientForContactMailbox();
expectDeassignContactFromOwnMailboxForUpload();
expectAssignContactToContactMailboxForUpload(remoteProperties);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactForUploadWhenContactUnpairsMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact unpairs their mailbox, the manager should destroy
// the client for the contact's mailbox and reassign the contact to
// our mailbox for upload.
expectDestroyClientForContactMailbox();
expectAssignContactToOwnMailboxForUpload();
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithoutMailbox));
// At shutdown the manager should destroy the client for our mailbox
// and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactForUploadWhenContactReplacesMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox and so does the
// contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When the contact replaces their mailbox, the manager should replace
// the client for the contact's mailbox and assign the contact to
// the contact's new mailbox for upload.
expectDestroyClientForContactMailbox();
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(
remotePropertiesForNewMailbox);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithNewMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testReassignsContactWhenContactReplacesMailbox()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox but the
// contact does.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to the contact's mailbox
// for upload and download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.startService();
// When the contact replaces their mailbox, the manager should replace
// the client for the contact's mailbox and assign the contact to
// the contact's new mailbox for upload and download.
expectDestroyClientForContactMailbox();
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(
remotePropertiesForNewMailbox);
expectAssignContactToContactMailboxForDownload(
remotePropertiesForNewMailbox);
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithNewMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testDoesNotReassignContactWhenRemotePropertiesAreUnchanged()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We don't have a mailbox but the
// contact does.
//
// We're online, so the manager should create a client for the
// contact's mailbox and assign the contact to the contact's mailbox
// for upload and download.
expectLoadUpdates(localUpdateWithoutMailbox,
remoteUpdateWithMailbox, null);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectAssignContactToContactMailboxForDownload(remoteProperties);
manager.startService();
// When the contact sends an update with unchanged properties, the
// clients and assignments should not be affected.
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithMailbox));
// At shutdown the manager should destroy the client for the contact's
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testAssignsContactToOurMailboxWhenClientVersionsBecomeCompatible()
throws Exception {
// At startup the manager should load the local and remote updates
// and our own mailbox properties. We have a mailbox but the contact
// doesn't. The contact's client API versions are incompatible with
// our mailbox.
//
// We're online, so the manager should create a client for our own
// mailbox. The contact's client API versions are incompatible with
// our mailbox, so the contact should not be assigned to our mailbox.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithIncompatibleClientVersions, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForOwnMailbox();
manager.startService();
// When the contact sends an update indicating that their client API
// versions are now compatible with our mailbox, the manager should
// assign the contact to our mailbox for upload and download.
expectAssignContactToOwnMailboxForUpload();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(new RemoteMailboxUpdateEvent(contact.getId(),
remoteUpdateWithoutMailbox));
// At shutdown the manager should destroy the client for our own
// mailbox and the reachability monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
@Test
public void testRecreatesClientsWhenOwnMailboxServerVersionsChange()
throws Exception {
long now = System.currentTimeMillis();
List<MailboxVersion> compatibleVersions =
new ArrayList<>(CLIENT_SUPPORTS);
compatibleVersions.add(new MailboxVersion(999, 0));
MailboxStatus mailboxStatus =
new MailboxStatus(now, now, 0, compatibleVersions);
// At startup the manager should load the local and remote updates
// and our own mailbox properties. The contact has a mailbox, so the
// remote update contains the properties received from the contact.
// We also have a mailbox, so the local update contains the properties
// we sent to the contact.
//
// We're online, so the manager should create clients for the
// contact's mailbox and our mailbox. The manager should assign the
// contact to the contact's mailbox for upload and our mailbox for
// download.
expectLoadUpdates(localUpdateWithMailbox,
remoteUpdateWithMailbox, ownProperties);
expectCheckPluginState(ACTIVE);
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.startService();
// When we learn that our mailbox's API versions have changed, the
// manager should destroy and recreate the clients for our own mailbox
// and the contact's mailbox and assign the contact to the new clients.
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectCreateClientForContactMailbox();
expectAssignContactToContactMailboxForUpload(remoteProperties);
expectCreateClientForOwnMailbox();
expectAssignContactToOwnMailboxForDownload();
manager.eventOccurred(
new OwnMailboxConnectionStatusEvent(mailboxStatus));
// At shutdown the manager should destroy the client for the contact's
// mailbox, the client for our own mailbox, and the reachability
// monitor.
expectRunTaskOnEventExecutor();
expectDestroyClientForContactMailbox();
expectDestroyClientForOwnMailbox();
expectDestroyReachabilityMonitor();
manager.stopService();
}
private void expectLoadUpdates(MailboxUpdate local,
@Nullable MailboxUpdate remote,
@Nullable MailboxProperties own) throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(reachabilityMonitor).start();
oneOf(dbExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(contactManager).getContacts(txn);
will(returnValue(singletonList(contact)));
oneOf(mailboxUpdateManager).getLocalUpdate(txn, contact.getId());
will(returnValue(local));
oneOf(mailboxUpdateManager).getRemoteUpdate(txn, contact.getId());
will(returnValue(remote));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(own));
}});
}
private void expectCheckPluginState(State state) {
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).getState();
will(returnValue(state));
}});
}
private void expectCreateClientForOwnMailbox() {
context.checking(new Expectations() {{
oneOf(mailboxClientFactory).createOwnMailboxClient(
reachabilityMonitor, ownProperties);
will(returnValue(ownClient));
oneOf(ownClient).start();
}});
}
private void expectCreateClientForContactMailbox() {
context.checking(new Expectations() {{
oneOf(mailboxClientFactory)
.createContactMailboxClient(reachabilityMonitor);
will(returnValue(contactClient));
oneOf(contactClient).start();
}});
}
private void expectAssignContactToOwnMailboxForDownload() {
context.checking(new Expectations() {{
oneOf(ownClient).assignContactForDownload(contact.getId(),
ownProperties,
requireNonNull(localProperties.getOutboxId()));
}});
}
private void expectAssignContactToOwnMailboxForUpload() {
context.checking(new Expectations() {{
oneOf(ownClient).assignContactForUpload(contact.getId(),
ownProperties,
requireNonNull(localProperties.getInboxId()));
}});
}
private void expectAssignContactToContactMailboxForDownload(
MailboxProperties remoteProperties) {
context.checking(new Expectations() {{
oneOf(contactClient).assignContactForDownload(contact.getId(),
remoteProperties,
requireNonNull(remoteProperties.getInboxId()));
}});
}
private void expectAssignContactToContactMailboxForUpload(
MailboxProperties remoteProperties) {
context.checking(new Expectations() {{
oneOf(contactClient).assignContactForUpload(contact.getId(),
remoteProperties,
requireNonNull(remoteProperties.getOutboxId()));
}});
}
private void expectDeassignContactFromOwnMailboxForUpload() {
context.checking(new Expectations() {{
oneOf(ownClient).deassignContactForUpload(contact.getId());
}});
}
private void expectDeassignContactFromOwnMailboxForDownload() {
context.checking(new Expectations() {{
oneOf(ownClient).deassignContactForDownload(contact.getId());
}});
}
private void expectDeassignContactFromContactMailboxForDownload() {
context.checking(new Expectations() {{
oneOf(contactClient).deassignContactForDownload(contact.getId());
}});
}
private void expectRunTaskOnEventExecutor() {
context.checking(new Expectations() {{
oneOf(eventExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
}});
}
private void expectDestroyClientForOwnMailbox() {
context.checking(new Expectations() {{
oneOf(ownClient).destroy();
}});
}
private void expectDestroyClientForContactMailbox() {
context.checking(new Expectations() {{
oneOf(contactClient).destroy();
}});
}
private void expectDestroyReachabilityMonitor() {
context.checking(new Expectations() {{
oneOf(reachabilityMonitor).destroy();
}});
}
}

View File

@@ -1,130 +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.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
abstract class MailboxDownloadWorkerTest<W extends MailboxDownloadWorker>
extends BrambleMockTestCase {
final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
final TorReachabilityMonitor torReachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
final MailboxApi mailboxApi = context.mock(MailboxApi.class);
final MailboxFileManager mailboxFileManager =
context.mock(MailboxFileManager.class);
private final Cancellable apiCall = context.mock(Cancellable.class);
private final long now = System.currentTimeMillis();
final MailboxFile file1 =
new MailboxFile(new MailboxFileId(getRandomId()), now - 1);
final MailboxFile file2 =
new MailboxFile(new MailboxFileId(getRandomId()), now);
final List<MailboxFile> files = asList(file1, file2);
private File testDir, tempFile;
MailboxProperties mailboxProperties;
W worker;
@Before
public void setUp() {
testDir = getTestDirectory();
tempFile = new File(testDir, "temp");
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
void expectStartConnectivityCheck() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
}
void expectStartTask(AtomicReference<ApiCall> task) {
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
void expectCheckForFoldersWithAvailableFiles(
List<MailboxFolderId> folderIds) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).getFolders(mailboxProperties);
will(returnValue(folderIds));
}});
}
void expectCheckForFiles(MailboxFolderId folderId,
List<MailboxFile> files) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties, folderId);
will(returnValue(files));
}});
}
void expectDownloadFile(MailboxFolderId folderId,
MailboxFile file)
throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties, folderId, file.name,
tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
}});
}
void expectDeleteFile(MailboxFolderId folderId, MailboxFile file,
boolean tolerableFailure) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteFile(mailboxProperties, folderId,
file.name);
if (tolerableFailure) {
will(throwException(new TolerableFailureException()));
}
}});
}
void expectAddReachabilityObserver() {
context.checking(new Expectations() {{
oneOf(torReachabilityMonitor).addOneShotObserver(worker);
}});
}
void expectRemoveObservers() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
}
}

View File

@@ -186,7 +186,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
MailboxFileId fileName1 = files1.get(0).name; MailboxFileId fileName1 = files1.get(0).name;
// owner can't check files // owner can't check files
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class, () ->
api.getFiles(ownerProperties, contact.inboxId)); api.getFiles(ownerProperties, contact.inboxId));
// contact downloads file // contact downloads file
@@ -197,13 +197,12 @@ public class MailboxIntegrationTest extends BrambleTestCase {
// owner can't download file, even if knowing name // owner can't download file, even if knowing name
File file1forbidden = folder.newFile(); File file1forbidden = folder.newFile();
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class, () -> api.getFile(ownerProperties,
api.getFile(ownerProperties, contact.inboxId, fileName1, contact.inboxId, fileName1, file1forbidden));
file1forbidden));
assertEquals(0, file1forbidden.length()); assertEquals(0, file1forbidden.length());
// owner can't delete file // owner can't delete file
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class, () ->
api.deleteFile(ownerProperties, contact.inboxId, fileName1)); api.deleteFile(ownerProperties, contact.inboxId, fileName1));
// contact deletes file // contact deletes file
@@ -233,7 +232,7 @@ public class MailboxIntegrationTest extends BrambleTestCase {
MailboxFileId file3name = files2.get(1).name; MailboxFileId file3name = files2.get(1).name;
// contact can't list files in contact's outbox // contact can't list files in contact's outbox
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class, () ->
api.getFiles(contactProperties, contact.outboxId)); api.getFiles(contactProperties, contact.outboxId));
// owner downloads both files from contact's outbox // owner downloads both files from contact's outbox
@@ -253,19 +252,17 @@ public class MailboxIntegrationTest extends BrambleTestCase {
// contact can't download files again, even if knowing name // contact can't download files again, even if knowing name
File file2forbidden = folder.newFile(); File file2forbidden = folder.newFile();
File file3forbidden = folder.newFile(); File file3forbidden = folder.newFile();
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class, () -> api.getFile(contactProperties,
api.getFile(contactProperties, contact.outboxId, file2name, contact.outboxId, file2name, file2forbidden));
file2forbidden)); assertThrows(ApiException.class, () -> api.getFile(contactProperties,
assertThrows(TolerableFailureException.class, () -> contact.outboxId, file3name, file3forbidden));
api.getFile(contactProperties, contact.outboxId, file3name,
file3forbidden));
assertEquals(0, file1forbidden.length()); assertEquals(0, file1forbidden.length());
assertEquals(0, file2forbidden.length()); assertEquals(0, file2forbidden.length());
// contact can't delete files in outbox // contact can't delete files in outbox
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class, () ->
api.deleteFile(contactProperties, contact.outboxId, file2name)); api.deleteFile(contactProperties, contact.outboxId, file2name));
assertThrows(TolerableFailureException.class, () -> assertThrows(ApiException.class, () ->
api.deleteFile(contactProperties, contact.outboxId, file3name)); api.deleteFile(contactProperties, contact.outboxId, file3name));
// owner deletes files // owner deletes files

View File

@@ -110,8 +110,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(mailboxSettingsManager).setOwnMailboxProperties( oneOf(mailboxSettingsManager).setOwnMailboxProperties(
with(txn), with(matches(ownerProperties))); with(txn), with(matches(ownerProperties)));
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time, oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time);
ownerProperties.getServerSupports());
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact1))); will(returnValue(singletonList(contact1)));
oneOf(mailboxUpdateManager).getRemoteUpdate(txn, oneOf(mailboxUpdateManager).getRemoteUpdate(txn,

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken; import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent; import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
@@ -19,6 +18,7 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
@@ -27,12 +27,10 @@ import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTIN
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE; import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
import static org.briarproject.bramble.test.TestUtils.getEvent;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.hasEvent; import static org.briarproject.bramble.test.TestUtils.hasEvent;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -41,7 +39,6 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
private final SettingsManager settingsManager = private final SettingsManager settingsManager =
context.mock(SettingsManager.class); context.mock(SettingsManager.class);
private final MailboxHook hook = context.mock(MailboxHook.class);
private final MailboxSettingsManager manager = private final MailboxSettingsManager manager =
new MailboxSettingsManagerImpl(settingsManager); new MailboxSettingsManagerImpl(settingsManager);
@@ -50,10 +47,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId()); private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
private final List<MailboxVersion> serverSupports = private final List<MailboxVersion> serverSupports =
asList(new MailboxVersion(1, 0), new MailboxVersion(1, 1)); asList(new MailboxVersion(1, 0), new MailboxVersion(1, 1));
private final MailboxProperties properties = new MailboxProperties(onion,
token, serverSupports);
private final int[] serverSupportsInts = {1, 0, 1, 1}; private final int[] serverSupportsInts = {1, 0, 1, 1};
private final Settings pairedSettings;
private final ContactId contactId1 = new ContactId(random.nextInt()); private final ContactId contactId1 = new ContactId(random.nextInt());
private final ContactId contactId2 = new ContactId(random.nextInt()); private final ContactId contactId2 = new ContactId(random.nextInt());
private final ContactId contactId3 = new ContactId(random.nextInt()); private final ContactId contactId3 = new ContactId(random.nextInt());
@@ -62,14 +56,6 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
private final long lastSuccess = now - 2345; private final long lastSuccess = now - 2345;
private final int attempts = 123; private final int attempts = 123;
public MailboxSettingsManagerImplTest() {
pairedSettings = new Settings();
pairedSettings.put(SETTINGS_KEY_ONION, onion);
pairedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
pairedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
serverSupportsInts);
}
@Test @Test
public void testReturnsNullPropertiesIfSettingsAreEmpty() throws Exception { public void testReturnsNullPropertiesIfSettingsAreEmpty() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
@@ -86,10 +72,14 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testReturnsProperties() throws Exception { public void testReturnsProperties() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
Settings settings = new Settings();
settings.put(SETTINGS_KEY_ONION, onion);
settings.put(SETTINGS_KEY_TOKEN, token.toString());
settings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE); oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(pairedSettings)); will(returnValue(settings));
}}); }});
MailboxProperties properties = manager.getOwnMailboxProperties(txn); MailboxProperties properties = manager.getOwnMailboxProperties(txn);
@@ -103,38 +93,20 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testStoresProperties() throws Exception { public void testStoresProperties() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
manager.registerMailboxHook(hook);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, pairedSettings,
SETTINGS_NAMESPACE);
oneOf(hook).mailboxPaired(txn, properties);
}});
manager.setOwnMailboxProperties(txn, properties);
}
@Test
public void testRemovesProperties() throws Exception {
Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings(); Settings expectedSettings = new Settings();
expectedSettings.put(SETTINGS_KEY_ONION, ""); expectedSettings.put(SETTINGS_KEY_ONION, onion);
expectedSettings.put(SETTINGS_KEY_TOKEN, ""); expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
expectedSettings.put(SETTINGS_KEY_ATTEMPTS, ""); expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
expectedSettings.put(SETTINGS_KEY_LAST_ATTEMPT, ""); serverSupportsInts);
expectedSettings.put(SETTINGS_KEY_LAST_SUCCESS, ""); MailboxProperties properties = new MailboxProperties(onion, token,
expectedSettings.put(SETTINGS_KEY_SERVER_SUPPORTS, ""); serverSupports);
manager.registerMailboxHook(hook);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings, oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE); SETTINGS_NAMESPACE);
oneOf(hook).mailboxUnpaired(txn);
}}); }});
manager.removeOwnMailboxProperties(txn); manager.setOwnMailboxProperties(txn, properties);
} }
@Test @Test
@@ -175,60 +147,63 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testRecordsSuccess() throws Exception { public void testRecordsSuccess() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Settings oldSettings = new Settings();
oldSettings
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, serverSupportsInts);
Settings expectedSettings = new Settings(); Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now); expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now); expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0); expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
serverSupportsInts);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE); oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(pairedSettings)); will(returnValue(oldSettings));
oneOf(settingsManager).mergeSettings(txn, expectedSettings, oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE); SETTINGS_NAMESPACE);
}}); }});
manager.recordSuccessfulConnection(txn, now, serverSupports); manager.recordSuccessfulConnection(txn, now);
OwnMailboxConnectionStatusEvent e = assertTrue(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
getEvent(txn, OwnMailboxConnectionStatusEvent.class);
MailboxStatus status = e.getStatus();
assertEquals(now, status.getTimeOfLastAttempt());
assertEquals(now, status.getTimeOfLastSuccess());
assertEquals(0, status.getAttemptsSinceSuccess());
assertFalse(status.hasProblem(now));
} }
@Test @Test
public void testDoesNotRecordSuccessIfNotPaired() throws Exception { public void testRecordsSuccessWithVersions() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
List<MailboxVersion> versions = singletonList(new MailboxVersion(2, 1));
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
expectedSettings.putInt(SETTINGS_KEY_SERVER_SUPPORTS, 0);
int[] newVersionsInts = {2, 1};
expectedSettings
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, newVersionsInts);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE); oneOf(settingsManager).mergeSettings(txn, expectedSettings,
will(returnValue(new Settings())); SETTINGS_NAMESPACE);
}}); }});
manager.recordSuccessfulConnection(txn, now, serverSupports); manager.recordSuccessfulConnection(txn, now, versions);
assertFalse(hasEvent(txn, OwnMailboxConnectionStatusEvent.class)); hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
} }
@Test @Test
public void testRecordsFailureOnFirstAttempt() throws Exception { public void testRecordsFailureOnFirstAttempt() throws Exception {
testRecordsFailure(pairedSettings, 0, 0); testRecordsFailure(new Settings(), 0);
} }
@Test @Test
public void testRecordsFailureOnLaterAttempt() throws Exception { public void testRecordsFailureOnLaterAttempt() throws Exception {
Settings oldSettings = new Settings(); Settings oldSettings = new Settings();
oldSettings.putAll(pairedSettings);
oldSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt); oldSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt);
oldSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess); oldSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess);
oldSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts); oldSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts);
testRecordsFailure(oldSettings, attempts, lastSuccess); testRecordsFailure(oldSettings, attempts);
} }
private void testRecordsFailure(Settings oldSettings, int oldAttempts, private void testRecordsFailure(Settings oldSettings, int oldAttempts)
long lastSuccess) throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings(); Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now); expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
@@ -242,25 +217,6 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
}}); }});
manager.recordFailedConnectionAttempt(txn, now); manager.recordFailedConnectionAttempt(txn, now);
OwnMailboxConnectionStatusEvent e =
getEvent(txn, OwnMailboxConnectionStatusEvent.class);
MailboxStatus status = e.getStatus();
assertEquals(now, status.getTimeOfLastAttempt());
assertEquals(lastSuccess, status.getTimeOfLastSuccess());
assertEquals(oldAttempts + 1, status.getAttemptsSinceSuccess());
}
@Test
public void testDoesNotRecordFailureIfNotPaired() throws Exception {
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(new Settings()));
}});
manager.recordFailedConnectionAttempt(txn, now);
assertFalse(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
} }
@Test @Test

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfEntry;
@@ -17,9 +16,6 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox; import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxPairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUnpairedEvent;
import org.briarproject.bramble.api.mailbox.event.MailboxUpdateSentToNewContactEvent;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent; import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -36,12 +32,9 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID; import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.GROUP_KEY_SENT_CLIENT_SUPPORTS; import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.GROUP_KEY_SENT_CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.GROUP_KEY_SENT_SERVER_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION; import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL; import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION; import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION;
@@ -52,7 +45,6 @@ import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE; import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getEvent;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties; import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
@@ -79,11 +71,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
private final MailboxSettingsManager mailboxSettingsManager = private final MailboxSettingsManager mailboxSettingsManager =
context.mock(MailboxSettingsManager.class); context.mock(MailboxSettingsManager.class);
private final Contact contact = getContact();
private final List<Contact> contacts = singletonList(contact);
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final GroupId contactGroupId = contactGroup.getId();
private final Message message = getMessage(contactGroupId);
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final BdfDictionary propsDict; private final BdfDictionary propsDict;
private final BdfDictionary emptyPropsDict = new BdfDictionary(); private final BdfDictionary emptyPropsDict = new BdfDictionary();
@@ -93,8 +80,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
private final BdfList newerClientSupports; private final BdfList newerClientSupports;
private final List<MailboxVersion> someServerSupportsList; private final List<MailboxVersion> someServerSupportsList;
private final BdfList someServerSupports; private final BdfList someServerSupports;
private final List<MailboxVersion> newerServerSupportsList;
private final BdfList newerServerSupports;
private final BdfList emptyServerSupports = new BdfList(); private final BdfList emptyServerSupports = new BdfList();
private final MailboxProperties updateProps; private final MailboxProperties updateProps;
private final MailboxUpdateWithMailbox updateWithMailbox; private final MailboxUpdateWithMailbox updateWithMailbox;
@@ -120,12 +105,6 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
someServerSupports = BdfList.of(BdfList.of( someServerSupports = BdfList.of(BdfList.of(
someServerSupportsList.get(0).getMajor(), someServerSupportsList.get(0).getMajor(),
someServerSupportsList.get(0).getMinor())); someServerSupportsList.get(0).getMinor()));
newerServerSupportsList = singletonList(new MailboxVersion(
someServerSupportsList.get(0).getMajor(),
someServerSupportsList.get(0).getMinor() + 1));
newerServerSupports = BdfList.of(BdfList.of(
newerServerSupportsList.get(0).getMajor(),
newerServerSupportsList.get(0).getMinor()));
updateNoMailbox = new MailboxUpdate(someClientSupportsList); updateNoMailbox = new MailboxUpdate(someClientSupportsList);
@@ -156,6 +135,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testCreatesGroupsAtUnpairedStartup() throws Exception { public void testCreatesGroupsAtUnpairedStartup() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>(); Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry( BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_CLIENT_SUPPORTS, GROUP_KEY_SENT_CLIENT_SUPPORTS,
@@ -177,8 +158,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contact.getId(), CLIENT_ID, MAJOR_VERSION); contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(SHARED)); will(returnValue(SHARED));
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroupId, SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroupId, oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId()); contact.getId());
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(null)); will(returnValue(null));
@@ -186,9 +167,9 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroupId, 1, someClientSupports, expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict); emptyServerSupports, emptyPropsDict);
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(), oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
@@ -197,14 +178,14 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.onDatabaseOpened(txn); t.onDatabaseOpened(txn);
assertFalse(hasEvent(txn, MailboxUpdateSentToNewContactEvent.class));
} }
@Test @Test
public void testCreatesGroupsAndCreatesAndSendsAtPairedStartup() public void testCreatesGroupsAndCreatesAndSendsAtPairedStartup()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>(); Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
BdfDictionary sentDict = BdfDictionary.of(new BdfEntry( BdfDictionary sentDict = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_CLIENT_SUPPORTS, GROUP_KEY_SENT_CLIENT_SUPPORTS,
@@ -226,8 +207,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contact.getId(), CLIENT_ID, MAJOR_VERSION); contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(SHARED)); will(returnValue(SHARED));
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroupId, SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroupId, oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId()); contact.getId());
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(ownProps)); will(returnValue(ownProps));
@@ -241,9 +222,9 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroupId, 1, someClientSupports, expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
someServerSupports, propsDict); someServerSupports, propsDict);
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(), oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
@@ -252,14 +233,14 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.onDatabaseOpened(txn); t.onDatabaseOpened(txn);
assertFalse(hasEvent(txn, MailboxUpdateSentToNewContactEvent.class));
} }
@Test @Test
public void testUnchangedClientSupportsOnSecondStartup() throws Exception { public void testUnchangedClientSupportsOnSecondStartup() throws Exception {
Transaction txn1 = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Transaction txn2 = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> emptyMessageMetadata = Map<MessageId, BdfDictionary> emptyMessageMetadata =
new LinkedHashMap<>(); new LinkedHashMap<>();
@@ -268,48 +249,46 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
someClientSupports)); someClientSupports));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).containsGroup(txn1, localGroup.getId()); oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(db).addGroup(txn1, localGroup); oneOf(db).addGroup(txn, localGroup);
oneOf(db).getContacts(txn1); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
// addingContact() // addingContact()
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).addGroup(txn1, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(clientVersioningManager).getClientVisibility(txn1, oneOf(clientVersioningManager).getClientVisibility(txn,
contact.getId(), CLIENT_ID, MAJOR_VERSION); contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(SHARED)); will(returnValue(SHARED));
oneOf(db).setGroupVisibility(txn1, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroupId, SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn1, contactGroupId, oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId()); contact.getId());
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn1); oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(null)); will(returnValue(null));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn1, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(emptyMessageMetadata)); will(returnValue(emptyMessageMetadata));
expectStoreMessage(txn1, contactGroupId, 1, someClientSupports, expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict); emptyServerSupports, emptyPropsDict);
oneOf(clientHelper).mergeGroupMetadata(txn1, localGroup.getId(), oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
sentDict); sentDict);
}}); }});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.onDatabaseOpened(txn1); t.onDatabaseOpened(txn);
assertFalse(hasEvent(txn1, MailboxUpdateSentToNewContactEvent.class));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).containsGroup(txn2, localGroup.getId()); oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(true)); will(returnValue(true));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn2, oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
localGroup.getId()); localGroup.getId());
will(returnValue(sentDict)); will(returnValue(sentDict));
oneOf(clientHelper).parseMailboxVersionList(someClientSupports); oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
@@ -317,16 +296,16 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
}}); }});
t = createInstance(someClientSupportsList); t = createInstance(someClientSupportsList);
t.onDatabaseOpened(txn2); t.onDatabaseOpened(txn);
assertFalse(hasEvent(txn2, MailboxUpdateSentToNewContactEvent.class));
} }
@Test @Test
public void testSendsUpdateWhenClientSupportsChangedOnSecondStartup() public void testSendsUpdateWhenClientSupportsChangedOnSecondStartup()
throws Exception { throws Exception {
Transaction txn1 = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Transaction txn2 = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> emptyMessageMetadata = Map<MessageId, BdfDictionary> emptyMessageMetadata =
new LinkedHashMap<>(); new LinkedHashMap<>();
@@ -335,43 +314,41 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
someClientSupports)); someClientSupports));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).containsGroup(txn1, localGroup.getId()); oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(db).addGroup(txn1, localGroup); oneOf(db).addGroup(txn, localGroup);
oneOf(db).getContacts(txn1); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
// addingContact() // addingContact()
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).addGroup(txn1, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(clientVersioningManager).getClientVisibility(txn1, oneOf(clientVersioningManager).getClientVisibility(txn,
contact.getId(), CLIENT_ID, MAJOR_VERSION); contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(SHARED)); will(returnValue(SHARED));
oneOf(db).setGroupVisibility(txn1, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroupId, SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn1, contactGroupId, oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId()); contact.getId());
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn1); oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(null)); will(returnValue(null));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn1, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(emptyMessageMetadata)); will(returnValue(emptyMessageMetadata));
expectStoreMessage(txn1, contactGroupId, 1, someClientSupports, expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict); emptyServerSupports, emptyPropsDict);
oneOf(clientHelper).mergeGroupMetadata(txn1, localGroup.getId(), oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
sentDict); sentDict);
}}); }});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.onDatabaseOpened(txn1); t.onDatabaseOpened(txn);
assertFalse(hasEvent(txn1, MailboxUpdateSentToNewContactEvent.class));
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry(MSG_KEY_VERSION, 1),
@@ -380,66 +357,66 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>(); Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
MessageId messageId = new MessageId(getRandomId()); MessageId messageId = new MessageId(getRandomId());
messageMetadata.put(messageId, metaDictionary); messageMetadata.put(messageId, metaDictionary);
BdfList oldBody = BdfList.of(1, someClientSupports, emptyServerSupports, BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
emptyPropsDict); propsDict);
BdfDictionary newerSentDict = BdfDictionary.of(new BdfEntry( BdfDictionary newerSentDict = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_CLIENT_SUPPORTS, GROUP_KEY_SENT_CLIENT_SUPPORTS,
newerClientSupports)); newerClientSupports));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).containsGroup(txn2, localGroup.getId()); oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(true)); will(returnValue(true));
// Find out that we are now on newerClientSupportsList // Find out that we are now on newerClientSupportsList
oneOf(clientHelper).getGroupMetadataAsDictionary(txn2, oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
localGroup.getId()); localGroup.getId());
will(returnValue(sentDict)); will(returnValue(sentDict));
oneOf(clientHelper).parseMailboxVersionList(someClientSupports); oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
will(returnValue(someClientSupportsList)); will(returnValue(someClientSupportsList));
oneOf(db).getContacts(txn2); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(db).getContact(txn2, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
// getLocalUpdate() // getLocalUpdate()
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn2, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn2, messageId); oneOf(clientHelper).getMessageAsList(txn, messageId);
will(returnValue(oldBody)); will(returnValue(body));
oneOf(clientHelper).parseAndValidateMailboxUpdate( oneOf(clientHelper).parseAndValidateMailboxUpdate(
someClientSupports, emptyServerSupports, emptyPropsDict); someClientSupports, someServerSupports, propsDict);
will(returnValue(updateNoMailbox)); will(returnValue(updateWithMailbox));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
// storeMessageReplaceLatest() // storeMessageReplaceLatest()
oneOf(clientHelper).getMessageMetadataAsDictionary(txn2, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
expectStoreMessage(txn2, contactGroupId, 2, expectStoreMessage(txn, contactGroup.getId(), 2,
newerClientSupports, emptyServerSupports, emptyPropsDict); newerClientSupports, someServerSupports, propsDict);
oneOf(db).removeMessage(txn2, messageId); oneOf(db).removeMessage(txn, messageId);
oneOf(clientHelper).mergeGroupMetadata(txn2, localGroup.getId(), oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
newerSentDict); newerSentDict);
}}); }});
t = createInstance(newerClientSupportsList); t = createInstance(newerClientSupportsList);
t.onDatabaseOpened(txn2); t.onDatabaseOpened(txn);
assertFalse(hasEvent(txn2, MailboxUpdateSentToNewContactEvent.class));
} }
@Test @Test
public void testCreatesContactGroupWhenAddingContactUnpaired() public void testCreatesContactGroupWhenAddingContactUnpaired()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>(); Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -452,8 +429,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contact.getId(), CLIENT_ID, MAJOR_VERSION); contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(SHARED)); will(returnValue(SHARED));
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroupId, SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroupId, oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId()); contact.getId());
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(null)); will(returnValue(null));
@@ -461,24 +438,22 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroupId, 1, someClientSupports, expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
emptyServerSupports, emptyPropsDict); emptyServerSupports, emptyPropsDict);
}}); }});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.addingContact(txn, contact); t.addingContact(txn, contact);
MailboxUpdateSentToNewContactEvent
e = getEvent(txn, MailboxUpdateSentToNewContactEvent.class);
assertNoMailboxPropertiesSent(e, someClientSupportsList);
} }
@Test @Test
public void testCreatesContactGroupAndCreatesAndSendsWhenAddingContactPaired() public void testCreatesContactGroupAndCreatesAndSendsWhenAddingContactPaired()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>(); Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -491,8 +466,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
contact.getId(), CLIENT_ID, MAJOR_VERSION); contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(SHARED)); will(returnValue(SHARED));
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroupId, SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroupId, oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId()); contact.getId());
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn); oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(ownProps)); will(returnValue(ownProps));
@@ -506,23 +481,21 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
expectStoreMessage(txn, contactGroupId, 1, someClientSupports, expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
someServerSupports, propsDict); someServerSupports, propsDict);
}}); }});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.addingContact(txn, contact); t.addingContact(txn, contact);
MailboxUpdateSentToNewContactEvent
e = getEvent(txn, MailboxUpdateSentToNewContactEvent.class);
assertMailboxPropertiesSent(e, someClientSupportsList);
} }
@Test @Test
public void testRemovesGroupWhenRemovingContact() throws Exception { public void testRemovesGroupWhenRemovingContact() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
@@ -539,6 +512,9 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered() public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
GroupId contactGroupId = new GroupId(getRandomId());
Message message = getMessage(contactGroupId);
BdfList body = BdfList.of(1, someClientSupports, someServerSupports, BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
propsDict); propsDict);
Metadata meta = new Metadata(); Metadata meta = new Metadata();
@@ -573,23 +549,16 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
assertEquals(ACCEPT_DO_NOT_SHARE, assertEquals(ACCEPT_DO_NOT_SHARE,
t.incomingMessage(txn, message, meta)); t.incomingMessage(txn, message, meta));
assertTrue(hasEvent(txn, RemoteMailboxUpdateEvent.class));
RemoteMailboxUpdateEvent e =
getEvent(txn, RemoteMailboxUpdateEvent.class);
assertEquals(contact.getId(), e.getContact());
MailboxUpdate u = e.getMailboxUpdate();
assertTrue(u.hasMailbox());
MailboxUpdateWithMailbox uMailbox = (MailboxUpdateWithMailbox) u;
assertEquals(updateWithMailbox.getClientSupports(),
uMailbox.getClientSupports());
assertEquals(updateWithMailbox.getMailboxProperties(),
uMailbox.getMailboxProperties());
} }
@Test @Test
public void testDeletesOlderUpdateWhenUpdateIsDelivered() public void testDeletesOlderUpdateWhenUpdateIsDelivered()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
GroupId contactGroupId = new GroupId(getRandomId());
Message message = getMessage(contactGroupId);
BdfList body = BdfList.of(1, someClientSupports, someServerSupports, BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
propsDict); propsDict);
Metadata meta = new Metadata(); Metadata meta = new Metadata();
@@ -632,22 +601,14 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
assertEquals(ACCEPT_DO_NOT_SHARE, assertEquals(ACCEPT_DO_NOT_SHARE,
t.incomingMessage(txn, message, meta)); t.incomingMessage(txn, message, meta));
assertTrue(hasEvent(txn, RemoteMailboxUpdateEvent.class));
RemoteMailboxUpdateEvent e =
getEvent(txn, RemoteMailboxUpdateEvent.class);
assertEquals(contact.getId(), e.getContact());
MailboxUpdate u = e.getMailboxUpdate();
assertTrue(u.hasMailbox());
MailboxUpdateWithMailbox uMailbox = (MailboxUpdateWithMailbox) u;
assertEquals(updateWithMailbox.getClientSupports(),
uMailbox.getClientSupports());
assertEquals(updateWithMailbox.getMailboxProperties(),
uMailbox.getMailboxProperties());
} }
@Test @Test
public void testDeletesObsoleteUpdateWhenDelivered() throws Exception { public void testDeletesObsoleteUpdateWhenDelivered() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
GroupId contactGroupId = new GroupId(getRandomId());
Message message = getMessage(contactGroupId);
Metadata meta = new Metadata(); Metadata meta = new Metadata();
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, 3), new BdfEntry(MSG_KEY_VERSION, 3),
@@ -674,13 +635,16 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
assertEquals(ACCEPT_DO_NOT_SHARE, assertEquals(ACCEPT_DO_NOT_SHARE,
t.incomingMessage(txn, message, meta)); t.incomingMessage(txn, message, meta));
assertFalse(hasEvent(txn, RemoteMailboxUpdateEvent.class)); assertFalse(hasEvent(txn, RemoteMailboxUpdateEvent.class));
} }
@Test @Test
public void testCreatesAndStoresLocalUpdateWithNewVersionOnPairing() public void testCreatesAndStoresLocalUpdateWithNewVersionOnPairing()
throws Exception { throws Exception {
Contact contact = getContact();
List<Contact> contacts = singletonList(contact);
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>(); Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
MessageId latestId = new MessageId(getRandomId()); MessageId latestId = new MessageId(getRandomId());
@@ -693,55 +657,37 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
new BdfEntry(MSG_KEY_VERSION, 3), new BdfEntry(MSG_KEY_VERSION, 3),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry(MSG_KEY_LOCAL, false)
)); ));
BdfDictionary groupMetadata = new BdfDictionary();
groupMetadata.put(GROUP_KEY_SENT_SERVER_SUPPORTS, someServerSupports);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(contacts)); will(returnValue(contacts));
// Generate mailbox properties for contact
oneOf(crypto).generateUniqueId(); oneOf(crypto).generateUniqueId();
will(returnValue(updateProps.getAuthToken())); will(returnValue(updateProps.getAuthToken()));
oneOf(crypto).generateUniqueId(); oneOf(crypto).generateUniqueId();
will(returnValue(updateProps.getInboxId())); will(returnValue(updateProps.getInboxId()));
oneOf(crypto).generateUniqueId(); oneOf(crypto).generateUniqueId();
will(returnValue(updateProps.getOutboxId())); will(returnValue(updateProps.getOutboxId()));
// Find latest update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
// Replace latest update with new update expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
expectStoreMessage(txn, contactGroupId, 2, someClientSupports,
someServerSupports, propsDict); someServerSupports, propsDict);
oneOf(db).removeMessage(txn, latestId); oneOf(db).removeMessage(txn, latestId);
// Store sent server-supported versions
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
groupMetadata);
}}); }});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.mailboxPaired(txn, ownProps); t.mailboxPaired(txn, ownProps.getOnion(), someServerSupportsList);
MailboxPairedEvent e = getEvent(txn, MailboxPairedEvent.class);
assertEquals(ownProps, e.getProperties());
Map<ContactId, MailboxUpdateWithMailbox> localUpdates =
e.getLocalUpdates();
assertEquals(singleton(contact.getId()), localUpdates.keySet());
MailboxUpdateWithMailbox u = localUpdates.get(contact.getId());
assertEquals(updateWithMailbox.getClientSupports(),
u.getClientSupports());
assertEquals(updateWithMailbox.getMailboxProperties(),
u.getMailboxProperties());
assertFalse(hasEvent(txn, MailboxUpdateSentToNewContactEvent.class));
} }
@Test @Test
public void testStoresLocalUpdateNoMailboxWithNewVersionOnUnpairing() public void testStoresLocalUpdateNoMailboxWithNewVersionOnUnpairing()
throws Exception { throws Exception {
Contact contact = getContact();
List<Contact> contacts = singletonList(contact);
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>(); Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
@@ -755,117 +701,30 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
new BdfEntry(MSG_KEY_VERSION, 3), new BdfEntry(MSG_KEY_VERSION, 3),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry(MSG_KEY_LOCAL, false)
)); ));
BdfDictionary groupMetadata = new BdfDictionary();
groupMetadata.put(GROUP_KEY_SENT_SERVER_SUPPORTS, NULL_VALUE);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(contacts)); will(returnValue(contacts));
// Find latest update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
// Replace latest update with new update expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
expectStoreMessage(txn, contactGroupId, 2, someClientSupports,
emptyServerSupports, emptyPropsDict); emptyServerSupports, emptyPropsDict);
oneOf(db).removeMessage(txn, latestId); oneOf(db).removeMessage(txn, latestId);
// Remove sent server-supported versions
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
groupMetadata);
}}); }});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList); MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.mailboxUnpaired(txn); t.mailboxUnpaired(txn);
MailboxUnpairedEvent e = getEvent(txn, MailboxUnpairedEvent.class);
Map<ContactId, MailboxUpdate> localUpdates = e.getLocalUpdates();
assertEquals(singleton(contact.getId()), localUpdates.keySet());
MailboxUpdate u = localUpdates.get(contact.getId());
assertFalse(u.hasMailbox());
assertFalse(hasEvent(txn, MailboxUpdateSentToNewContactEvent.class));
}
@Test
public void testStoresLocalUpdateWhenServerSupportsChange()
throws Exception {
Transaction txn = new Transaction(null, false);
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
MessageId latestId = new MessageId(getRandomId());
messageMetadata.put(latestId, BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, 1),
new BdfEntry(MSG_KEY_LOCAL, true)
));
BdfList body = BdfList.of(1, someClientSupports, someServerSupports,
propsDict);
BdfDictionary oldGroupMetadata = new BdfDictionary();
oldGroupMetadata.put(GROUP_KEY_SENT_SERVER_SUPPORTS,
someServerSupports);
BdfDictionary newGroupMetadata = new BdfDictionary();
newGroupMetadata.put(GROUP_KEY_SENT_SERVER_SUPPORTS,
newerServerSupports);
context.checking(new Expectations() {{
// Load sent server-supported versions
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
localGroup.getId());
will(returnValue(oldGroupMetadata));
oneOf(clientHelper).parseMailboxVersionList(someServerSupports);
will(returnValue(someServerSupportsList));
// Update sent server-supported versions
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
newGroupMetadata);
oneOf(db).getContacts(txn);
will(returnValue(contacts));
// Find latest update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId);
will(returnValue(messageMetadata));
// Load and parse latest update
oneOf(clientHelper).getMessageAsList(txn, latestId);
will(returnValue(body));
oneOf(clientHelper).parseAndValidateMailboxUpdate(
someClientSupports, someServerSupports, propsDict);
will(returnValue(updateWithMailbox));
// Replace latest update with new update
expectStoreMessage(txn, contactGroupId, 2, someClientSupports,
newerServerSupports, propsDict);
oneOf(db).removeMessage(txn, latestId);
}});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.serverSupportedVersionsReceived(txn, newerServerSupportsList);
}
@Test
public void testDoesNotStoreLocalUpdateWhenServerSupportsAreUnchanged()
throws Exception {
Transaction txn = new Transaction(null, false);
BdfDictionary groupMetadata = new BdfDictionary();
groupMetadata.put(GROUP_KEY_SENT_SERVER_SUPPORTS, someServerSupports);
context.checking(new Expectations() {{
// Load sent server-supported versions
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
localGroup.getId());
will(returnValue(groupMetadata));
oneOf(clientHelper).parseMailboxVersionList(someServerSupports);
will(returnValue(someServerSupportsList));
}});
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
t.serverSupportedVersionsReceived(txn, someServerSupportsList);
} }
@Test @Test
public void testGetRemoteUpdate() throws Exception { public void testGetRemoteUpdate() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry(MSG_KEY_VERSION, 1),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry(MSG_KEY_LOCAL, false)
@@ -883,7 +742,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
.createContactGroup(CLIENT_ID, MAJOR_VERSION, contact); .createContactGroup(CLIENT_ID, MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, messageId); oneOf(clientHelper).getMessageAsList(txn, messageId);
will(returnValue(body)); will(returnValue(body));
@@ -901,6 +760,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
public void testGetRemoteUpdateReturnsNullBecauseNoUpdate() public void testGetRemoteUpdateReturnsNullBecauseNoUpdate()
throws Exception { throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> emptyMessageMetadata = Map<MessageId, BdfDictionary> emptyMessageMetadata =
new LinkedHashMap<>(); new LinkedHashMap<>();
@@ -911,7 +772,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(emptyMessageMetadata)); will(returnValue(emptyMessageMetadata));
}}); }});
@@ -922,6 +783,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testGetRemoteUpdateNoMailbox() throws Exception { public void testGetRemoteUpdateNoMailbox() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry(MSG_KEY_VERSION, 1),
new BdfEntry(MSG_KEY_LOCAL, false) new BdfEntry(MSG_KEY_LOCAL, false)
@@ -939,7 +802,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, messageId); oneOf(clientHelper).getMessageAsList(txn, messageId);
will(returnValue(body)); will(returnValue(body));
@@ -956,6 +819,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testGetLocalUpdate() throws Exception { public void testGetLocalUpdate() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry(MSG_KEY_VERSION, 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry(MSG_KEY_LOCAL, true)
@@ -973,7 +838,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, messageId); oneOf(clientHelper).getMessageAsList(txn, messageId);
will(returnValue(body)); will(returnValue(body));
@@ -990,6 +855,8 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testGetLocalUpdateNoMailbox() throws Exception { public void testGetLocalUpdateNoMailbox() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact();
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
BdfDictionary metaDictionary = BdfDictionary.of( BdfDictionary metaDictionary = BdfDictionary.of(
new BdfEntry(MSG_KEY_VERSION, 1), new BdfEntry(MSG_KEY_VERSION, 1),
new BdfEntry(MSG_KEY_LOCAL, true) new BdfEntry(MSG_KEY_LOCAL, true)
@@ -1007,7 +874,7 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
MAJOR_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroupId); contactGroup.getId());
will(returnValue(messageMetadata)); will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, messageId); oneOf(clientHelper).getMessageAsList(txn, messageId);
will(returnValue(body)); will(returnValue(body));
@@ -1042,24 +909,4 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
false); false);
}}); }});
} }
private void assertNoMailboxPropertiesSent(
MailboxUpdateSentToNewContactEvent e,
List<MailboxVersion> clientSupports) {
assertEquals(contact.getId(), e.getContactId());
MailboxUpdate u = e.getMailboxUpdate();
assertEquals(clientSupports, u.getClientSupports());
assertFalse(u.hasMailbox());
}
private void assertMailboxPropertiesSent(
MailboxUpdateSentToNewContactEvent e,
List<MailboxVersion> clientSupports) {
assertEquals(contact.getId(), e.getContactId());
MailboxUpdate u = e.getMailboxUpdate();
assertEquals(clientSupports, u.getClientSupports());
assertTrue(u.hasMailbox());
MailboxUpdateWithMailbox uMailbox = (MailboxUpdateWithMailbox) u;
assertEquals(updateProps, uMailbox.getMailboxProperties());
}
} }

View File

@@ -1,16 +1,12 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable; 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.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.mailbox.MailboxFolderId; import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord; import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent; import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
@@ -29,13 +25,10 @@ import org.junit.Test;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS; import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY; import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
@@ -57,8 +50,6 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
private final TaskScheduler taskScheduler = private final TaskScheduler taskScheduler =
context.mock(TaskScheduler.class); context.mock(TaskScheduler.class);
private final EventBus eventBus = context.mock(EventBus.class); private final EventBus eventBus = context.mock(EventBus.class);
private final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
private final ConnectivityChecker connectivityChecker = private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class); context.mock(ConnectivityChecker.class);
private final MailboxApiCaller mailboxApiCaller = private final MailboxApiCaller mailboxApiCaller =
@@ -81,9 +72,6 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
private final MessageId ackedId = new MessageId(getRandomId()); private final MessageId ackedId = new MessageId(getRandomId());
private final MessageId sentId = new MessageId(getRandomId()); private final MessageId sentId = new MessageId(getRandomId());
private final MessageId newMessageId = new MessageId(getRandomId()); private final MessageId newMessageId = new MessageId(getRandomId());
private final GroupId groupId = new GroupId(getRandomId());
private final Map<ContactId, Boolean> groupVisibility =
singletonMap(contactId, true);
private File testDir, tempFile; private File testDir, tempFile;
private MailboxUploadWorker worker; private MailboxUploadWorker worker;
@@ -93,9 +81,8 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
testDir = getTestDirectory(); testDir = getTestDirectory();
tempFile = new File(testDir, "temp"); tempFile = new File(testDir, "temp");
worker = new MailboxUploadWorker(ioExecutor, db, clock, taskScheduler, worker = new MailboxUploadWorker(ioExecutor, db, clock, taskScheduler,
eventBus, connectionRegistry, connectivityChecker, eventBus, connectivityChecker, mailboxApiCaller, mailboxApi,
mailboxApiCaller, mailboxApi, mailboxFileManager, mailboxFileManager, mailboxProperties, folderId, contactId);
mailboxProperties, folderId, contactId);
} }
@After @After
@@ -106,11 +93,8 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
@Test @Test
public void testChecksForDataWhenStartedAndRemovesObserverWhenDestroyed() public void testChecksForDataWhenStartedAndRemovesObserverWhenDestroyed()
throws Exception { throws Exception {
// When the worker is started it should check the connection registry. // When the worker is started it should check for data to send
// We're not connected to the contact, so the worker should check for
// data to send
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting(); expectCheckForDataToSendNoDataWaiting();
worker.start(); worker.start();
@@ -122,59 +106,15 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
worker.destroy(); worker.destroy();
} }
@Test
public void testDoesNotCheckForDataWhenStartedIfConnectedToContact() {
// When the worker is started it should check the connection registry.
// We're connected to the contact, so the worker should not check for
// data to send
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(true);
worker.start();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testChecksForDataWhenContactDisconnects() throws Exception {
// When the worker is started it should check the connection registry.
// We're connected to the contact, so the worker should not check for
// data to send
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(true);
worker.start();
// When the contact disconnects, the worker should start a task to
// check for data to send
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting();
worker.eventOccurred(new ContactDisconnectedEvent(contactId));
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test @Test
public void testChecksConnectivityWhenStartedIfDataIsReady() public void testChecksConnectivityWhenStartedIfDataIsReady()
throws Exception { throws Exception {
Transaction recordTxn = new Transaction(null, false); Transaction recordTxn = new Transaction(null, false);
// When the worker is started it should check the connection registry. // When the worker is started it should check for data to send. As
// We're not connected to the contact, so the worker should check for // there's data ready to send immediately, the worker should start a
// data to send. As there's data ready to send immediately, the worker // connectivity check
// should start a connectivity check
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendAndStartConnectivityCheck(); expectCheckForDataToSendAndStartConnectivityCheck();
worker.start(); worker.start();
@@ -209,9 +149,7 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
worker.onConnectivityCheckSucceeded(); worker.onConnectivityCheckSucceeded();
// When the upload task runs, it should upload the file, record // When the upload task runs, it should upload the file, record
// the acked/sent messages in the DB, and check the connection // the acked/sent messages in the DB, and check for more data to send
// registry. We're not connected to the contact, so the worker should
// check for more data to send
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(mailboxApi).addFile(mailboxProperties, folderId, tempFile); oneOf(mailboxApi).addFile(mailboxProperties, folderId, tempFile);
oneOf(db).transaction(with(false), withDbRunnable(recordTxn)); oneOf(db).transaction(with(false), withDbRunnable(recordTxn));
@@ -219,7 +157,6 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
oneOf(db).setMessagesSent(recordTxn, contactId, oneOf(db).setMessagesSent(recordTxn, contactId,
singletonList(sentId), MAX_LATENCY); singletonList(sentId), MAX_LATENCY);
}}); }});
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting(); expectCheckForDataToSendNoDataWaiting();
assertFalse(upload.get().callApi()); assertFalse(upload.get().callApi());
@@ -234,42 +171,12 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
assertFalse(tempFile.exists()); assertFalse(tempFile.exists());
} }
@Test
public void testDoesNotWriteFileIfContactConnectsDuringConnectivityCheck()
throws Exception {
// When the worker is started it should check the connection registry.
// We're not connected to the contact, so the worker should check for
// data to send. As there's data ready to send immediately, the worker
// should start a connectivity check
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendAndStartConnectivityCheck();
worker.start();
// Before the connectivity check succeeds, we make a direct connection
// to the contact
worker.eventOccurred(new ContactConnectedEvent(contactId));
// When the connectivity check succeeds, the worker should not start
// writing and uploading a file
worker.onConnectivityCheckSucceeded();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test @Test
public void testCancelsApiCallWhenDestroyed() throws Exception { public void testCancelsApiCallWhenDestroyed() throws Exception {
// When the worker is started it should check the connection registry. // When the worker is started it should check for data to send. As
// We're not connected to the contact, so the worker should check for // there's data ready to send immediately, the worker should start a
// data to send. As there's data ready to send immediately, the worker // connectivity check
// should start a connectivity check
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendAndStartConnectivityCheck(); expectCheckForDataToSendAndStartConnectivityCheck();
worker.start(); worker.start();
@@ -305,7 +212,9 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
// When the worker is destroyed it should remove the connectivity // When the worker is destroyed it should remove the connectivity
// observer and event listener and cancel the upload task // observer and event listener and cancel the upload task
expectCancelTask(apiCall); context.checking(new Expectations() {{
oneOf(apiCall).cancel();
}});
expectRemoveObserverAndListener(); expectRemoveObserverAndListener();
worker.destroy(); worker.destroy();
@@ -321,21 +230,16 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
@Test @Test
public void testSchedulesWakeupWhenStartedIfDataIsNotReady() public void testSchedulesWakeupWhenStartedIfDataIsNotReady()
throws Exception { throws Exception {
// When the worker is started it should check the connection registry. // When the worker is started it should check for data to send. As
// We're not connected to the contact, so the worker should check for // the data isn't ready to send immediately, the worker should
// data to send. As the data isn't ready to send immediately, the // schedule a wakeup
// worker should schedule a wakeup
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>(); AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendAndScheduleWakeup(wakeup); expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start(); worker.start();
// When the wakeup task runs it should check the connection registry. // When the wakeup task runs it should check for data to send
// We're not connected to the contact, so the worker should check for
// data to send
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting(); expectCheckForDataToSendNoDataWaiting();
wakeup.get().run(); wakeup.get().run();
@@ -348,51 +252,21 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
} }
@Test @Test
public void testCancelsWakeupIfContactConnectsBeforeWakingUp() public void testCancelsWakeupIfDestroyedBeforeWakingUp() throws Exception {
throws Exception {
// When the worker is started it should check for data to send. As // When the worker is started it should check for data to send. As
// the data isn't ready to send immediately, the worker should // the data isn't ready to send immediately, the worker should
// schedule a wakeup // schedule a wakeup
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>(); AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start();
// Before the wakeup task runs, we make a direct connection to the
// contact. The worker should cancel the wakeup task
expectCancelTask(wakeupTask);
worker.eventOccurred(new ContactConnectedEvent(contactId));
// If the wakeup task runs anyway (cancellation came too late), it
// should return without doing anything
wakeup.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testCancelsWakeupIfDestroyedBeforeWakingUp() throws Exception {
// When the worker is started it should check the connection registry.
// We're not connected to the contact, so the worker should check for
// data to send. As the data isn't ready to send immediately, the
// worker should schedule a wakeup
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup); expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start(); worker.start();
// When the worker is destroyed it should cancel the wakeup and // When the worker is destroyed it should cancel the wakeup and
// remove the connectivity observer and event listener // remove the connectivity observer and event listener
expectCancelTask(wakeupTask); context.checking(new Expectations() {{
oneOf(wakeupTask).cancel();
}});
expectRemoveObserverAndListener(); expectRemoveObserverAndListener();
worker.destroy(); worker.destroy();
@@ -405,12 +279,10 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
@Test @Test
public void testCancelsWakeupIfEventIsReceivedBeforeWakingUp() public void testCancelsWakeupIfEventIsReceivedBeforeWakingUp()
throws Exception { throws Exception {
// When the worker is started it should check the connection registry. // When the worker is started it should check for data to send. As
// We're not connected to the contact, so the worker should check for // the data isn't ready to send immediately, the worker should
// data to send. As the data isn't ready to send immediately, the // schedule a wakeup
// worker should schedule a wakeup
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
AtomicReference<Runnable> wakeup = new AtomicReference<>(); AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup); expectCheckForDataToSendAndScheduleWakeup(wakeup);
@@ -421,10 +293,11 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
// wakeup task and schedule a check for new data after a short delay // wakeup task and schedule a check for new data after a short delay
AtomicReference<Runnable> check = new AtomicReference<>(); AtomicReference<Runnable> check = new AtomicReference<>();
expectScheduleCheck(check, CHECK_DELAY_MS); expectScheduleCheck(check, CHECK_DELAY_MS);
expectCancelTask(wakeupTask); context.checking(new Expectations() {{
oneOf(wakeupTask).cancel();
}});
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId, worker.eventOccurred(new MessageSharedEvent(newMessageId));
groupVisibility));
// If the wakeup task runs anyway (cancellation came too late), it // If the wakeup task runs anyway (cancellation came too late), it
// should return early when it finds the state has changed // should return early when it finds the state has changed
@@ -433,13 +306,9 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
// Before the check task runs, the worker receives another event that // Before the check task runs, the worker receives another event that
// indicates new data may be available. The event should be ignored, // indicates new data may be available. The event should be ignored,
// as a check for new data has already been scheduled // as a check for new data has already been scheduled
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId, worker.eventOccurred(new MessageSharedEvent(newMessageId));
groupVisibility));
// When the check task runs, it should check the connection registry. // When the check task runs, it should check for new data
// We're not connected to the contact, so the worker should check for
// new data
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting(); expectCheckForDataToSendNoDataWaiting();
check.get().run(); check.get().run();
@@ -453,11 +322,8 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
@Test @Test
public void testCancelsCheckWhenDestroyed() throws Exception { public void testCancelsCheckWhenDestroyed() throws Exception {
// When the worker is started it should check the connection registry. // When the worker is started it should check for data to send
// We're not connected to the contact, so the worker should check for
// data to send
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting(); expectCheckForDataToSendNoDataWaiting();
worker.start(); worker.start();
@@ -468,12 +334,13 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
AtomicReference<Runnable> check = new AtomicReference<>(); AtomicReference<Runnable> check = new AtomicReference<>();
expectScheduleCheck(check, CHECK_DELAY_MS); expectScheduleCheck(check, CHECK_DELAY_MS);
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId, worker.eventOccurred(new MessageSharedEvent(newMessageId));
groupVisibility));
// When the worker is destroyed it should cancel the check and // When the worker is destroyed it should cancel the check and
// remove the connectivity observer and event listener // remove the connectivity observer and event listener
expectCancelTask(checkTask); context.checking(new Expectations() {{
oneOf(checkTask).cancel();
}});
expectRemoveObserverAndListener(); expectRemoveObserverAndListener();
worker.destroy(); worker.destroy();
@@ -483,100 +350,13 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
check.get().run(); check.get().run();
} }
@Test
public void testCancelsCheckIfContactConnects() throws Exception {
// When the worker is started it should check the connection registry.
// We're not connected to the contact, so the worker should check for
// data to send
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting();
worker.start();
// The worker receives an event that indicates new data may be
// available. The worker should schedule a check for new data after
// a short delay
AtomicReference<Runnable> check = new AtomicReference<>();
expectScheduleCheck(check, CHECK_DELAY_MS);
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId,
groupVisibility));
// Before the check task runs, we make a direct connection to the
// contact. The worker should cancel the check
expectCancelTask(checkTask);
worker.eventOccurred(new ContactConnectedEvent(contactId));
// If the check runs anyway (cancellation came too late), it should
// return early when it finds the state has changed
check.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testDoesNotScheduleCheckIfGroupIsVisible() throws Exception {
// When the worker is started it should check the connection registry.
// We're not connected to the contact, so the worker should check for
// data to send
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting();
worker.start();
// The worker receives an event that indicates new data may be
// available. The group is visible to the contact but not shared, so
// the worker should not schedule a check for new data
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId,
singletonMap(contactId, false)));
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testDoesNotScheduleCheckIfGroupIsInvisible() throws Exception {
// When the worker is started it should check the connection registry.
// We're not connected to the contact, so the worker should check for
// data to send
expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting();
worker.start();
// The worker receives an event that indicates new data may be
// available. The group is not visible to the contact, so the worker
// should not schedule a check for new data
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId,
emptyMap()));
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test @Test
public void testRetriesAfterDelayIfExceptionOccursWhileWritingFile() public void testRetriesAfterDelayIfExceptionOccursWhileWritingFile()
throws Exception { throws Exception {
// When the worker is started it should check the connection registry. // When the worker is started it should check for data to send. As
// We're not connected to the contact, so the worker should check for // there's data ready to send immediately, the worker should start a
// data to send. As there's data ready to send immediately, the worker // connectivity check
// should start a connectivity check
expectRunTaskOnIoExecutor(); expectRunTaskOnIoExecutor();
expectCheckConnectionRegistry(false);
expectCheckForDataToSendAndStartConnectivityCheck(); expectCheckForDataToSendAndStartConnectivityCheck();
worker.start(); worker.start();
@@ -595,10 +375,7 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
worker.onConnectivityCheckSucceeded(); worker.onConnectivityCheckSucceeded();
// When the check task runs it should check the connection registry. // When the check task runs it should check for new data
// We're not connected to the contact, so the worker should check for
// new data
expectCheckConnectionRegistry(false);
expectCheckForDataToSendNoDataWaiting(); expectCheckForDataToSendNoDataWaiting();
check.get().run(); check.get().run();
@@ -610,13 +387,6 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
worker.destroy(); worker.destroy();
} }
private void expectCheckConnectionRegistry(boolean connected) {
context.checking(new Expectations() {{
oneOf(connectionRegistry).isConnected(contactId);
will(returnValue(connected));
}});
}
private void expectRunTaskOnIoExecutor() { private void expectRunTaskOnIoExecutor() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class))); oneOf(ioExecutor).execute(with(any(Runnable.class)));
@@ -686,12 +456,6 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
}}); }});
} }
private void expectCancelTask(Cancellable task) {
context.checking(new Expectations() {{
oneOf(task).cancel();
}});
}
private void expectRemoveObserverAndListener() { private void expectRemoveObserverAndListener() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker); oneOf(connectivityChecker).removeObserver(worker);

View File

@@ -1,321 +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.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Test;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.mailbox.OwnMailboxClient.CONNECTIVITY_CHECK_INTERVAL_MS;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
public class OwnMailboxClientTest extends BrambleMockTestCase {
private final MailboxWorkerFactory workerFactory =
context.mock(MailboxWorkerFactory.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final TorReachabilityMonitor reachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final TaskScheduler taskScheduler =
context.mock(TaskScheduler.class);
private final Executor ioExecutor = context.mock(Executor.class);
private final MailboxWorker contactListWorker =
context.mock(MailboxWorker.class, "contactListWorker");
private final MailboxWorker uploadWorker1 =
context.mock(MailboxWorker.class, "uploadWorker1");
private final MailboxWorker uploadWorker2 =
context.mock(MailboxWorker.class, "uploadWorker2");
private final MailboxWorker downloadWorker =
context.mock(MailboxWorker.class, "downloadWorker");
private final Cancellable connectivityCheck =
context.mock(Cancellable.class);
private final MailboxProperties properties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final MailboxFolderId folderId = new MailboxFolderId(getRandomId());
private final ContactId contactId1 = getContactId();
private final ContactId contactId2 = getContactId();
private final OwnMailboxClient client;
public OwnMailboxClientTest() {
expectCreateContactListWorker();
client = new OwnMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor, taskScheduler, ioExecutor, properties);
context.assertIsSatisfied();
}
@Test
public void testStartAndDestroyWithNoContactsAssigned() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignContactForUploadAndDestroyClient() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateUploadWorker(contactId1, uploadWorker1);
expectStartWorker(uploadWorker1);
client.assignContactForUpload(contactId1, properties, folderId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(uploadWorker1);
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignAndDeassignContactForUpload() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateUploadWorker(contactId1, uploadWorker1);
expectStartWorker(uploadWorker1);
client.assignContactForUpload(contactId1, properties, folderId);
// When the contact is deassigned, the worker should be destroyed
expectDestroyWorker(uploadWorker1);
client.deassignContactForUpload(contactId1);
context.assertIsSatisfied();
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignAndDeassignTwoContactsForUpload() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
// When the first contact is assigned, the first worker should be
// created and started
expectCreateUploadWorker(contactId1, uploadWorker1);
expectStartWorker(uploadWorker1);
client.assignContactForUpload(contactId1, properties, folderId);
// When the second contact is assigned, the second worker should be
// created and started
expectCreateUploadWorker(contactId2, uploadWorker2);
expectStartWorker(uploadWorker2);
client.assignContactForUpload(contactId2, properties, folderId);
// When the second contact is deassigned, the second worker should be
// destroyed
expectDestroyWorker(uploadWorker2);
client.deassignContactForUpload(contactId2);
context.assertIsSatisfied();
// When the first contact is deassigned, the first worker should be
// destroyed
expectDestroyWorker(uploadWorker1);
client.deassignContactForUpload(contactId1);
context.assertIsSatisfied();
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignContactForDownloadAndDestroyClient() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateDownloadWorker();
expectStartWorker(downloadWorker);
client.assignContactForDownload(contactId1, properties, folderId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(downloadWorker);
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testAssignAndDeassignTwoContactsForDownload() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
// When the first contact is assigned, the worker should be created and
// started
expectCreateDownloadWorker();
expectStartWorker(downloadWorker);
client.assignContactForDownload(contactId1, properties, folderId);
// When the second contact is assigned, nothing should happen to the
// worker
client.assignContactForDownload(contactId2, properties, folderId);
// When the first contact is deassigned, nothing should happen to the
// worker
client.deassignContactForDownload(contactId1);
// When the second contact is deassigned, the worker should be
// destroyed
expectDestroyWorker(downloadWorker);
client.deassignContactForDownload(contactId2);
context.assertIsSatisfied();
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
client.destroy();
}
@Test
public void testCancelsConnectivityCheckWhenClientIsDestroyed() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
// When the first connectivity check succeeds, the worker should
// schedule a second check
AtomicReference<Runnable> task = new AtomicReference<>();
expectScheduleConnectivityCheck(task);
client.onConnectivityCheckSucceeded();
// When the task runs, the worker should check the mailbox's
// connectivity
expectStartConnectivityCheck();
task.get().run();
// When the second connectivity check succeeds, the worker should
// schedule a third check
expectScheduleConnectivityCheck(task);
client.onConnectivityCheckSucceeded();
// When the client is destroyed, the scheduled check should be cancelled
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
context.checking(new Expectations() {{
oneOf(connectivityCheck).cancel();
}});
client.destroy();
// If the task runs anyway (cancellation came too late), it should
// return when it finds that the client has been destroyed
task.get().run();
}
@Test
public void testIgnoresConnectivityResultWhenClientIsDestroyed() {
expectStartConnectivityCheck();
expectStartWorker(contactListWorker);
client.start();
// When the first connectivity check succeeds, the worker should
// schedule a second check
AtomicReference<Runnable> task = new AtomicReference<>();
expectScheduleConnectivityCheck(task);
client.onConnectivityCheckSucceeded();
// When the task runs, the worker should check the mailbox's
// connectivity
expectStartConnectivityCheck();
task.get().run();
// Before the connectivity check succeeds, the client is destroyed
expectDestroyWorker(contactListWorker);
expectDestroyConnectivityChecker();
client.destroy();
// If the connectivity check succeeds despite the connectivity checker
// having been destroyed, the client should not schedule another check
client.onConnectivityCheckSucceeded();
}
private void expectCreateContactListWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createContactListWorkerForOwnMailbox(
connectivityChecker, properties);
will(returnValue(contactListWorker));
}});
}
private void expectCreateUploadWorker(ContactId contactId,
MailboxWorker worker) {
context.checking(new Expectations() {{
oneOf(workerFactory).createUploadWorker(connectivityChecker,
properties, folderId, contactId);
will(returnValue(worker));
}});
}
private void expectCreateDownloadWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createDownloadWorkerForOwnMailbox(
connectivityChecker, reachabilityMonitor, properties);
will(returnValue(downloadWorker));
}});
}
private void expectStartWorker(MailboxWorker worker) {
context.checking(new Expectations() {{
oneOf(worker).start();
}});
}
private void expectStartConnectivityCheck() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(properties, client);
}});
}
private void expectScheduleConnectivityCheck(
AtomicReference<Runnable> task) {
context.checking(new Expectations() {{
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(CONNECTIVITY_CHECK_INTERVAL_MS),
with(TimeUnit.MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(task, Runnable.class, 0),
returnValue(connectivityCheck)
));
}});
}
private void expectDestroyWorker(MailboxWorker worker) {
context.checking(new Expectations() {{
oneOf(worker).destroy();
}});
}
private void expectDestroyConnectivityChecker() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).destroy();
}});
}
}

View File

@@ -1,236 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.mailbox.MailboxDownloadWorker.FolderFile;
import static org.briarproject.bramble.mailbox.OwnMailboxDownloadWorker.MAX_ROUND_ROBIN_FILES;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class OwnMailboxDownloadWorkerTest
extends MailboxDownloadWorkerTest<OwnMailboxDownloadWorker> {
private final MailboxFolderId folderId1 =
new MailboxFolderId(getRandomId());
private final MailboxFolderId folderId2 =
new MailboxFolderId(getRandomId());
private final List<MailboxFolderId> folderIds =
asList(folderId1, folderId2);
public OwnMailboxDownloadWorkerTest() {
mailboxProperties = getMailboxProperties(true, CLIENT_SUPPORTS);
worker = new OwnMailboxDownloadWorker(connectivityChecker,
torReachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, mailboxProperties);
}
@Override
public void setUp() {
super.setUp();
}
@Test
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the worker is destroyed it should remove the connectivity
// and reachability observers
expectRemoveObservers();
worker.destroy();
}
@Test
public void testChecksForFilesWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, a list-folders task should be
// started for the first download cycle
AtomicReference<ApiCall> listFoldersTask = new AtomicReference<>();
expectStartTask(listFoldersTask);
worker.onConnectivityCheckSucceeded();
// When the list-folders tasks runs and finds no folders with files
// to download, it should add a Tor reachability observer
expectCheckForFoldersWithAvailableFiles(emptyList());
expectAddReachabilityObserver();
assertFalse(listFoldersTask.get().callApi());
// When the reachability observer is called, a list-folders task should
// be started for the second download cycle
expectStartTask(listFoldersTask);
worker.onTorReachable();
// When the list-folders tasks runs and finds no folders with files
// to download, it should finish the second download cycle
expectCheckForFoldersWithAvailableFiles(emptyList());
assertFalse(listFoldersTask.get().callApi());
// When the worker is destroyed it should remove the connectivity
// and reachability observers
expectRemoveObservers();
worker.destroy();
}
@Test
public void testDownloadsFilesWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, a list-folders task should be
// started for the first download cycle
AtomicReference<ApiCall> listFoldersTask = new AtomicReference<>();
expectStartTask(listFoldersTask);
worker.onConnectivityCheckSucceeded();
// When the list-folders tasks runs and finds some folders with files
// to download, it should start a list-files task for the first folder
AtomicReference<ApiCall> listFilesTask = new AtomicReference<>();
expectCheckForFoldersWithAvailableFiles(folderIds);
expectStartTask(listFilesTask);
assertFalse(listFoldersTask.get().callApi());
// When the first list-files task runs and finds no files to download,
// it should start a second list-files task for the next folder
expectCheckForFiles(folderId1, emptyList());
expectStartTask(listFilesTask);
assertFalse(listFilesTask.get().callApi());
// When the second list-files task runs and finds some files to
// download, it should create the round-robin queue and start a
// download task for the first file
AtomicReference<ApiCall> downloadTask = new AtomicReference<>();
expectCheckForFiles(folderId2, files);
expectStartTask(downloadTask);
assertFalse(listFilesTask.get().callApi());
// When the first download task runs it should download the file to the
// location provided by the file manager and start a delete task
AtomicReference<ApiCall> deleteTask = new AtomicReference<>();
expectDownloadFile(folderId2, file1);
expectStartTask(deleteTask);
assertFalse(downloadTask.get().callApi());
// When the first delete task runs it should delete the file, ignore
// the tolerable failure, and start a download task for the next file
expectDeleteFile(folderId2, file1, true); // Delete fails tolerably
expectStartTask(downloadTask);
assertFalse(deleteTask.get().callApi());
// When the second download task runs it should download the file to
// the location provided by the file manager and start a delete task
expectDownloadFile(folderId2, file2);
expectStartTask(deleteTask);
assertFalse(downloadTask.get().callApi());
// When the second delete task runs it should delete the file and
// start a list-inbox task to check for files that may have arrived
// since the first download cycle started
expectDeleteFile(folderId2, file2, false); // Delete succeeds
expectStartTask(listFoldersTask);
assertFalse(deleteTask.get().callApi());
// When the list-inbox tasks runs and finds no more files to download,
// it should add a Tor reachability observer
expectCheckForFoldersWithAvailableFiles(emptyList());
expectAddReachabilityObserver();
assertFalse(listFoldersTask.get().callApi());
// When the reachability observer is called, a list-inbox task should
// be started for the second download cycle
expectStartTask(listFoldersTask);
worker.onTorReachable();
// When the list-inbox tasks runs and finds no more files to download,
// it should finish the second download cycle
expectCheckForFoldersWithAvailableFiles(emptyList());
assertFalse(listFoldersTask.get().callApi());
// When the worker is destroyed it should remove the connectivity
// and reachability observers
expectRemoveObservers();
worker.destroy();
}
@Test
public void testRoundRobinQueueVisitsAllFolders() {
// Ten folders with two files each
Map<MailboxFolderId, Queue<MailboxFile>> available =
createAvailableFiles(10, 2);
Queue<FolderFile> queue = worker.createRoundRobinQueue(available);
// Check that all files were queued
for (MailboxFolderId folderId : available.keySet()) {
assertEquals(2, countFilesWithFolderId(queue, folderId));
}
}
@Test
public void testSizeOfRoundRobinQueueIsLimited() {
// Two folders with MAX_ROUND_ROBIN_FILES each
Map<MailboxFolderId, Queue<MailboxFile>> available =
createAvailableFiles(2, MAX_ROUND_ROBIN_FILES);
Queue<FolderFile> queue = worker.createRoundRobinQueue(available);
// Check that half the files in each folder were queued
for (MailboxFolderId folderId : available.keySet()) {
assertEquals(MAX_ROUND_ROBIN_FILES / 2,
countFilesWithFolderId(queue, folderId));
}
}
private Map<MailboxFolderId, Queue<MailboxFile>> createAvailableFiles(
int numFolders, int numFiles) {
Map<MailboxFolderId, Queue<MailboxFile>> available = new HashMap<>();
List<MailboxFolderId> folderIds = createFolderIds(numFolders);
for (MailboxFolderId folderId : folderIds) {
available.put(folderId, createFiles(numFiles));
}
return available;
}
private List<MailboxFolderId> createFolderIds(int size) {
List<MailboxFolderId> folderIds = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
folderIds.add(new MailboxFolderId(getRandomId()));
}
return folderIds;
}
private Queue<MailboxFile> createFiles(int size) {
Queue<MailboxFile> files = new LinkedList<>();
for (int i = 0; i < size; i++) {
files.add(new MailboxFile(new MailboxFileId(getRandomId()), i));
}
return files;
}
private int countFilesWithFolderId(Queue<FolderFile> queue,
MailboxFolderId folderId) {
int count = 0;
for (FolderFile file : queue) {
if (file.folderId.equals(folderId)) count++;
}
return count;
}
}

View File

@@ -12,11 +12,9 @@ import org.briarproject.bramble.event.DefaultEventExecutorModule;
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule; import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule; import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestDnsModule;
import org.briarproject.bramble.test.TestFeatureFlagModule; import org.briarproject.bramble.test.TestFeatureFlagModule;
import org.briarproject.bramble.test.TestMailboxDirectoryModule; import org.briarproject.bramble.test.TestMailboxDirectoryModule;
import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.test.TestSecureRandomModule;
import org.briarproject.bramble.test.TestSocksModule;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -29,14 +27,12 @@ import dagger.Component;
DefaultEventExecutorModule.class, DefaultEventExecutorModule.class,
DefaultWakefulIoExecutorModule.class, DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class, TestDatabaseConfigModule.class,
TestDnsModule.class,
TestFeatureFlagModule.class, TestFeatureFlagModule.class,
TestMailboxDirectoryModule.class, TestMailboxDirectoryModule.class,
RemovableDriveIntegrationTestModule.class, RemovableDriveIntegrationTestModule.class,
RemovableDriveModule.class, RemovableDriveModule.class,
TestSecureRandomModule.class, TestSecureRandomModule.class,
TimeTravelModule.class, TimeTravelModule.class
TestSocksModule.class
}) })
interface RemovableDriveIntegrationTestComponent interface RemovableDriveIntegrationTestComponent
extends BrambleCoreEagerSingletons { extends BrambleCoreEagerSingletons {

View File

@@ -12,19 +12,16 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4; import static org.briarproject.bramble.plugin.tor.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; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DPI_BRIDGES; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DPI_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class CircumventionProviderImplTest extends BrambleTestCase { public class CircumventionProviderTest extends BrambleTestCase {
private final CircumventionProviderImpl provider = private final CircumventionProvider provider =
new CircumventionProviderImpl(); new CircumventionProviderImpl();
@Test @Test
@@ -59,31 +56,13 @@ public class CircumventionProviderImplTest extends BrambleTestCase {
provider.getSuitableBridgeTypes(country)); provider.getSuitableBridgeTypes(country));
} }
for (String country : DPI_BRIDGES) { for (String country : DPI_BRIDGES) {
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE), assertEquals(asList(NON_DEFAULT_OBFS4, MEEK),
provider.getSuitableBridgeTypes(country)); provider.getSuitableBridgeTypes(country));
} }
assertEquals(asList(DEFAULT_OBFS4, VANILLA), assertEquals(asList(DEFAULT_OBFS4, VANILLA),
provider.getSuitableBridgeTypes("ZZ")); provider.getSuitableBridgeTypes("ZZ"));
} }
@Test
public void testHasSnowflakeParamsWithLetsEncrypt() {
testHasSnowflakeParams(true);
}
@Test
public void testHasSnowflakeParamsWithoutLetsEncrypt() {
testHasSnowflakeParams(false);
}
private void testHasSnowflakeParams(boolean letsEncrypt) {
String tmParams = provider.getSnowflakeParams("TM", letsEncrypt);
String defaultParams = provider.getSnowflakeParams("ZZ", letsEncrypt);
assertFalse(tmParams.isEmpty());
assertFalse(defaultParams.isEmpty());
assertNotEquals(defaultParams, tmParams);
}
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) { private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
Set<T> intersection = new HashSet<>(a); Set<T> intersection = new HashSet<>(a);
intersection.retainAll(b); intersection.retainAll(b);

View File

@@ -14,13 +14,11 @@ import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.Versions; import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@@ -70,10 +68,13 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
oneOf(eventBus).addListener(session); oneOf(eventBus).addListener(session);
// Send the protocol versions // Send the protocol versions
oneOf(recordWriter).writeVersions(with(any(Versions.class))); oneOf(recordWriter).writeVersions(with(any(Versions.class)));
// Calculate capacity for acks
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes));
// No messages to ack // No messages to ack
oneOf(db).transactionWithResult(with(true), oneOf(db).transactionWithResult(with(true),
withDbCallable(noAckIdTxn)); withDbCallable(noAckIdTxn));
oneOf(db).getMessagesToAck(noAckIdTxn, contactId); oneOf(db).getMessagesToAck(noAckIdTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(emptyList())); will(returnValue(emptyList()));
// Calculate capacity for messages // Calculate capacity for messages
oneOf(recordWriter).getBytesWritten(); oneOf(recordWriter).getBytesWritten();
@@ -105,6 +106,7 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
MAX_FILE_PAYLOAD_BYTES); MAX_FILE_PAYLOAD_BYTES);
Transaction ackIdTxn = new Transaction(null, true); Transaction ackIdTxn = new Transaction(null, true);
Transaction noAckIdTxn = new Transaction(null, true);
Transaction msgIdTxn = new Transaction(null, true); Transaction msgIdTxn = new Transaction(null, true);
Transaction msgTxn = new Transaction(null, true); Transaction msgTxn = new Transaction(null, true);
@@ -112,24 +114,28 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
long capacityForMessages = long capacityForMessages =
MAX_FILE_PAYLOAD_BYTES - versionRecordBytes - ackRecordBytes; MAX_FILE_PAYLOAD_BYTES - versionRecordBytes - ackRecordBytes;
AtomicReference<Ack> ack = new AtomicReference<>();
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
// Add listener // Add listener
oneOf(eventBus).addListener(session); oneOf(eventBus).addListener(session);
// Send the protocol versions // Send the protocol versions
oneOf(recordWriter).writeVersions(with(any(Versions.class))); oneOf(recordWriter).writeVersions(with(any(Versions.class)));
// Load the IDs to ack
oneOf(db).transactionWithResult(with(true),
withDbCallable(ackIdTxn));
oneOf(db).getMessagesToAck(ackIdTxn, contactId);
will(returnValue(singletonList(message.getId())));
// Calculate capacity for acks // Calculate capacity for acks
oneOf(recordWriter).getBytesWritten(); oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes)); will(returnValue((long) versionRecordBytes));
// One message to ack
oneOf(db).transactionWithResult(with(true),
withDbCallable(ackIdTxn));
oneOf(db).getMessagesToAck(ackIdTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(singletonList(message.getId())));
// Send the ack // Send the ack
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes));
oneOf(recordWriter).writeAck(with(any(Ack.class))); oneOf(recordWriter).writeAck(with(any(Ack.class)));
will(new CaptureArgumentAction<>(ack, Ack.class, 0)); // No more messages to ack
oneOf(db).transactionWithResult(with(true),
withDbCallable(noAckIdTxn));
oneOf(db).getMessagesToAck(noAckIdTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(emptyList()));
// Calculate capacity for messages // Calculate capacity for messages
oneOf(recordWriter).getBytesWritten(); oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes + ackRecordBytes)); will(returnValue((long) versionRecordBytes + ackRecordBytes));
@@ -156,7 +162,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
assertEquals(singletonList(message.getId()), assertEquals(singletonList(message.getId()),
sessionRecord.getAckedIds()); sessionRecord.getAckedIds());
assertEquals(singletonList(message.getId()), ack.get().getMessageIds());
assertEquals(singletonList(message1.getId()), assertEquals(singletonList(message1.getId()),
sessionRecord.getSentIds()); sessionRecord.getSentIds());
} }
@@ -173,50 +178,48 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
eventBus, contactId, transportId, MAX_LATENCY, eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter, sessionRecord, capacity); streamWriter, recordWriter, sessionRecord, capacity);
Transaction ackIdTxn = new Transaction(null, true); Transaction ackIdTxn1 = new Transaction(null, true);
Transaction ackIdTxn2 = new Transaction(null, true);
int firstAckRecordBytes = int firstAckRecordBytes =
RECORD_HEADER_BYTES + MessageId.LENGTH * MAX_MESSAGE_IDS; RECORD_HEADER_BYTES + MessageId.LENGTH * MAX_MESSAGE_IDS;
int secondAckRecordBytes = RECORD_HEADER_BYTES + MessageId.LENGTH; int secondAckRecordBytes = RECORD_HEADER_BYTES + MessageId.LENGTH;
// There are MAX_MESSAGE_IDS + 2 messages that need to be acked, but List<MessageId> idsInFirstAck = new ArrayList<>(MAX_MESSAGE_IDS);
// only enough capacity to ack MAX_MESSAGE_IDS + 1 messages for (int i = 0; i < MAX_MESSAGE_IDS; i++) {
List<MessageId> idsToAck = new ArrayList<>(MAX_MESSAGE_IDS + 2); idsInFirstAck.add(new MessageId(getRandomId()));
for (int i = 0; i < MAX_MESSAGE_IDS + 2; i++) {
idsToAck.add(new MessageId(getRandomId()));
} }
// The first ack contains MAX_MESSAGE_IDS IDs
List<MessageId> idsInFirstAck = idsToAck.subList(0, MAX_MESSAGE_IDS);
// The second ack contains one ID
List<MessageId> idsInSecondAck = List<MessageId> idsInSecondAck =
idsToAck.subList(MAX_MESSAGE_IDS, MAX_MESSAGE_IDS + 1); singletonList(new MessageId(getRandomId()));
List<MessageId> idsAcked = idsToAck.subList(0, MAX_MESSAGE_IDS + 1); List<MessageId> allIds = new ArrayList<>(MAX_MESSAGE_IDS + 1);
allIds.addAll(idsInFirstAck);
AtomicReference<Ack> firstAck = new AtomicReference<>(); allIds.addAll(idsInSecondAck);
AtomicReference<Ack> secondAck = new AtomicReference<>();
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
// Add listener // Add listener
oneOf(eventBus).addListener(session); oneOf(eventBus).addListener(session);
// Send the protocol versions // Send the protocol versions
oneOf(recordWriter).writeVersions(with(any(Versions.class))); oneOf(recordWriter).writeVersions(with(any(Versions.class)));
// Load the IDs to ack
oneOf(db).transactionWithResult(with(true),
withDbCallable(ackIdTxn));
oneOf(db).getMessagesToAck(ackIdTxn, contactId);
will(returnValue(idsToAck));
// Calculate capacity for acks // Calculate capacity for acks
oneOf(recordWriter).getBytesWritten(); oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes)); will(returnValue((long) versionRecordBytes));
// Load the IDs for the first ack record
oneOf(db).transactionWithResult(with(true),
withDbCallable(ackIdTxn1));
oneOf(db).getMessagesToAck(ackIdTxn1, contactId, MAX_MESSAGE_IDS);
will(returnValue(idsInFirstAck));
// Send the first ack record // Send the first ack record
oneOf(recordWriter).writeAck(with(any(Ack.class))); oneOf(recordWriter).writeAck(with(any(Ack.class)));
will(new CaptureArgumentAction<>(firstAck, Ack.class, 0));
// Calculate remaining capacity for acks // Calculate remaining capacity for acks
oneOf(recordWriter).getBytesWritten(); oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes + firstAckRecordBytes)); will(returnValue((long) versionRecordBytes + firstAckRecordBytes));
// Load the IDs for the second ack record
oneOf(db).transactionWithResult(with(true),
withDbCallable(ackIdTxn2));
oneOf(db).getMessagesToAck(ackIdTxn2, contactId, 1);
will(returnValue(idsInSecondAck));
// Send the second ack record // Send the second ack record
oneOf(recordWriter).writeAck(with(any(Ack.class))); oneOf(recordWriter).writeAck(with(any(Ack.class)));
will(new CaptureArgumentAction<>(secondAck, Ack.class, 0));
// Not enough capacity left for another ack // Not enough capacity left for another ack
oneOf(recordWriter).getBytesWritten(); oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes + firstAckRecordBytes will(returnValue((long) versionRecordBytes + firstAckRecordBytes
@@ -233,9 +236,7 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
session.run(); session.run();
assertEquals(idsAcked, sessionRecord.getAckedIds()); assertEquals(allIds, sessionRecord.getAckedIds());
assertEquals(idsInFirstAck, firstAck.get().getMessageIds());
assertEquals(idsInSecondAck, secondAck.get().getMessageIds());
assertEquals(emptyList(), sessionRecord.getSentIds()); assertEquals(emptyList(), sessionRecord.getSentIds());
} }
} }

View File

@@ -3,8 +3,6 @@ package org.briarproject.bramble.sync;
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons; import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
import org.briarproject.bramble.test.TestDnsModule;
import org.briarproject.bramble.test.TestSocksModule;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -13,9 +11,7 @@ import dagger.Component;
@Singleton @Singleton
@Component(modules = { @Component(modules = {
BrambleCoreIntegrationTestModule.class, BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class, BrambleCoreModule.class
TestDnsModule.class,
TestSocksModule.class
}) })
interface SyncIntegrationTestComponent extends interface SyncIntegrationTestComponent extends
BrambleCoreIntegrationTestEagerSingletons { BrambleCoreIntegrationTestEagerSingletons {

View File

@@ -14,9 +14,7 @@ import dagger.Component;
@Singleton @Singleton
@Component(modules = { @Component(modules = {
BrambleCoreIntegrationTestModule.class, BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class, BrambleCoreModule.class
TestDnsModule.class,
TestSocksModule.class
}) })
public interface BrambleIntegrationTestComponent public interface BrambleIntegrationTestComponent
extends BrambleCoreIntegrationTestEagerSingletons { extends BrambleCoreIntegrationTestEagerSingletons {

View File

@@ -9,8 +9,6 @@ import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
import org.briarproject.bramble.test.BrambleIntegrationTestComponent; import org.briarproject.bramble.test.BrambleIntegrationTestComponent;
import org.briarproject.bramble.test.TestDnsModule;
import org.briarproject.bramble.test.TestSocksModule;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -19,9 +17,7 @@ import dagger.Component;
@Singleton @Singleton
@Component(modules = { @Component(modules = {
BrambleCoreIntegrationTestModule.class, BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class, BrambleCoreModule.class
TestDnsModule.class,
TestSocksModule.class
}) })
interface TransportKeyAgreementTestComponent interface TransportKeyAgreementTestComponent
extends BrambleIntegrationTestComponent { extends BrambleIntegrationTestComponent {

View File

@@ -35,7 +35,7 @@ dependencyVerification {
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', 'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38', 'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38',
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b', 'org.briarproject:jtorctl:0.4:jtorctl-0.4.jar:4e61f59dc9f3984438a7151c4df8d7c1f83d5fb3eb8c151acfc794a8fef85a36',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', '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:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',

View File

@@ -21,8 +21,6 @@ dependencies {
tor "org.briarproject:tor-windows:$tor_version" tor "org.briarproject:tor-windows:$tor_version"
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version" tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version" tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
tor "org.briarproject:snowflake-linux:$snowflake_version"
tor "org.briarproject:snowflake-windows:$snowflake_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble; package org.briarproject.bramble;
import org.briarproject.bramble.io.DnsModule;
import org.briarproject.bramble.network.JavaNetworkModule; import org.briarproject.bramble.network.JavaNetworkModule;
import org.briarproject.bramble.plugin.tor.CircumventionModule; import org.briarproject.bramble.plugin.tor.CircumventionModule;
import org.briarproject.bramble.socks.SocksModule; import org.briarproject.bramble.socks.SocksModule;
@@ -10,7 +9,6 @@ import dagger.Module;
@Module(includes = { @Module(includes = {
CircumventionModule.class, CircumventionModule.class,
DnsModule.class,
JavaNetworkModule.class, JavaNetworkModule.class,
JavaSystemModule.class, JavaSystemModule.class,
SocksModule.class SocksModule.class

View File

@@ -9,8 +9,6 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
@@ -44,10 +42,11 @@ import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4; import static org.briarproject.bramble.plugin.tor.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; import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -59,8 +58,6 @@ import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class BridgeTest extends BrambleTestCase { public class BridgeTest extends BrambleTestCase {
private static final String[] SNOWFLAKE_COUNTRY_CODES = {"TM", "ZZ"};
@Parameters @Parameters
public static Iterable<Params> data() { public static Iterable<Params> data() {
BrambleJavaIntegrationTestComponent component = BrambleJavaIntegrationTestComponent component =
@@ -72,39 +69,29 @@ public class BridgeTest extends BrambleTestCase {
CircumventionProvider provider = component.getCircumventionProvider(); CircumventionProvider provider = component.getCircumventionProvider();
List<Params> states = new ArrayList<>(); List<Params> states = new ArrayList<>();
for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) { for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) {
for (String bridge : for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
provider.getBridges(DEFAULT_OBFS4, "", true)) {
states.add(new Params(bridge, DEFAULT_OBFS4, stats, false)); states.add(new Params(bridge, DEFAULT_OBFS4, stats, false));
} }
for (String bridge : for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
provider.getBridges(NON_DEFAULT_OBFS4, "", true)) { states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats, false));
states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats,
false));
} }
for (String bridge : provider.getBridges(VANILLA, "", true)) { for (String bridge : provider.getBridges(VANILLA)) {
states.add(new Params(bridge, VANILLA, stats, false)); states.add(new Params(bridge, VANILLA, stats, false));
} }
for (String bridge : provider.getBridges(MEEK, "", true)) { for (String bridge : provider.getBridges(MEEK)) {
states.add(new Params(bridge, MEEK, stats, true)); states.add(new Params(bridge, MEEK, stats, true));
} }
for (String countryCode : SNOWFLAKE_COUNTRY_CODES) {
for (String bridge :
provider.getBridges(SNOWFLAKE, countryCode, true)) {
states.add(new Params(bridge, SNOWFLAKE, stats, true));
}
for (String bridge :
provider.getBridges(SNOWFLAKE, countryCode, false)) {
states.add(new Params(bridge, SNOWFLAKE, stats, true));
}
}
} }
return states; return states;
} }
private final static long TIMEOUT = MINUTES.toMillis(2); private final static long OBFS4_TIMEOUT = MINUTES.toMillis(2);
private final static long MEEK_TIMEOUT = MINUTES.toMillis(6); private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
private final static int UNREACHABLE_BRIDGES_ALLOWED = 6; private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
private final static int ATTEMPTS_PER_BRIDGE = 5; private final static int ATTEMPTS_PER_BRIDGE = 5;
// Use different ports from Briar Desktop to avoid conflicts
private final static int SOCKS_PORT = DEFAULT_SOCKS_PORT + 10;
private final static int CONTROL_PORT = DEFAULT_CONTROL_PORT + 10;
private final static Logger LOG = getLogger(BridgeTest.class.getName()); private final static Logger LOG = getLogger(BridgeTest.class.getName());
@@ -128,12 +115,6 @@ public class BridgeTest extends BrambleTestCase {
Clock clock; Clock clock;
@Inject @Inject
CryptoComponent crypto; CryptoComponent crypto;
@Inject
@TorSocksPort
int torSocksPort;
@Inject
@TorControlPort
int torControlPort;
private final File torDir = getTestDirectory(); private final File torDir = getTestDirectory();
private final Params params; private final Params params;
@@ -179,16 +160,15 @@ public class BridgeTest extends BrambleTestCase {
} }
@Override @Override
public List<String> getBridges(BridgeType bridgeType, public List<String> getBridges(BridgeType bridgeType) {
String countryCode, boolean letsEncrypt) {
return singletonList(params.bridge); return singletonList(params.bridge);
} }
}; };
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor, factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, eventBus, torSocketFactory, networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, bridgeProvider, backoffFactory, resourceProvider, bridgeProvider,
batteryManager, clock, crypto, torDir, torSocksPort, batteryManager, clock, crypto, torDir,
torControlPort); SOCKS_PORT, CONTROL_PORT);
} }
@After @After
@@ -207,7 +187,8 @@ public class BridgeTest extends BrambleTestCase {
try { try {
plugin.start(); plugin.start();
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
long timeout = params.bridgeType == MEEK ? MEEK_TIMEOUT : TIMEOUT; long timeout = params.bridgeType == MEEK
? MEEK_TIMEOUT : OBFS4_TIMEOUT;
while (clock.currentTimeMillis() - start < timeout) { while (clock.currentTimeMillis() - start < timeout) {
if (plugin.getState() == ACTIVE) return; if (plugin.getState() == ACTIVE) return;
clock.sleep(500); clock.sleep(500);

View File

@@ -14,8 +14,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
BrambleCoreIntegrationTestModule.class, BrambleCoreIntegrationTestModule.class,
BrambleCoreModule.class, BrambleCoreModule.class,
BrambleJavaModule.class, BrambleJavaModule.class
TestTorPortsModule.class
}) })
public interface BrambleJavaIntegrationTestComponent public interface BrambleJavaIntegrationTestComponent
extends BrambleCoreIntegrationTestEagerSingletons { extends BrambleCoreIntegrationTestEagerSingletons {

View File

@@ -1,26 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
@Module
class TestTorPortsModule {
@Provides
@TorSocksPort
int provideTorSocksPort() {
return DEFAULT_SOCKS_PORT + 10;
}
@Provides
@TorControlPort
int provideTorControlPort() {
return DEFAULT_CONTROL_PORT + 10;
}
}

View File

@@ -24,12 +24,10 @@ dependencyVerification {
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0', 'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', 'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy-linux:0.0.14:obfs4proxy-linux-0.0.14.jar:6391d323d45a279362236c7c62e21b903d07d4f31f5e0c8d49d009769b720cc6', 'org.briarproject:obfs4proxy-linux:0.0.12:obfs4proxy-linux-0.0.12.jar:3dd83aff25fe1cb3e4eab78a02c76ac921f552be6877b3af83a472438525df2a',
'org.briarproject:obfs4proxy-windows:0.0.14:obfs4proxy-windows-0.0.14.jar:801d48525f52583a470a1671026b87992176d4432b299774989387cb87bc8ba3', 'org.briarproject:obfs4proxy-windows:0.0.12:obfs4proxy-windows-0.0.12.jar:392aa4b9d9c6fef0c659c4068d019d6c6471991bbb62ff00553884ec36018c7b',
'org.briarproject:snowflake-linux:2.3.1:snowflake-linux-2.3.1.jar:99ecf4546d8f79eb8408168c09380fec596558ac934554bf7d4247ea7ef2c9f3', 'org.briarproject:tor-linux:0.4.5.12-2:tor-linux-0.4.5.12-2.jar:d275f323faf5e70b33d2c8a1bdab1bb3ab5a0d8f4e23c4a6dda03d86f4e95838',
'org.briarproject:snowflake-windows:2.3.1:snowflake-windows-2.3.1.jar:d011f1a72c00a221f56380c19aad8ff11db8c2bb1adb0784125572d80b4d275a', 'org.briarproject:tor-windows:0.4.5.12-2:tor-windows-0.4.5.12-2.jar:46599a15d099ed35a360113293f66acc119571c24ec2e37e85e4fb54b4722e07',
'org.briarproject:tor-linux:0.4.5.14:tor-linux-0.4.5.14.jar:1844e54cf6df0c85cec219381a3364c759ae444a6b63f7558b757becb7d41d08',
'org.briarproject:tor-windows:0.4.5.14:tor-windows-0.4.5.14.jar:d337afa1043f0cfa7e6e8c2473d682a5663a2c8052bb97a770450893c78c9b4f',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', '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:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53', 'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -26,10 +26,9 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10412 versionCode 10409
versionName "1.4.12" versionName "1.4.9"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "TorVersion", "\"$tor_version\""
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",

View File

@@ -785,7 +785,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
NotificationCompat.Builder b = new NotificationCompat.Builder( NotificationCompat.Builder b = new NotificationCompat.Builder(
appContext, MAILBOX_PROBLEM_CHANNEL_ID); appContext, MAILBOX_PROBLEM_CHANNEL_ID);
b.setSmallIcon(R.drawable.notification_mailbox); b.setSmallIcon(R.drawable.ic_mailbox);
b.setColor(getColor(appContext, R.color.briar_red_500)); b.setColor(getColor(appContext, R.color.briar_red_500));
b.setContentTitle( b.setContentTitle(
appContext.getText(R.string.mailbox_error_notification_title)); appContext.getText(R.string.mailbox_error_notification_title));

View File

@@ -6,6 +6,8 @@ import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.StrictMode; import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy; import android.os.StrictMode.ThreadPolicy;
import android.os.StrictMode.VmPolicy; import android.os.StrictMode.VmPolicy;
@@ -120,12 +122,62 @@ public class BriarApplicationImpl extends Application
} }
private void enableStrictMode() { private void enableStrictMode() {
int targetSdk = Build.VERSION.SDK_INT;
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder(); ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
threadPolicy.detectAll();
// pulled in from threadPolicy.detectAll();
threadPolicy.detectDiskReads();
threadPolicy.detectDiskWrites();
threadPolicy.detectNetwork();
if (targetSdk >= VERSION_CODES.HONEYCOMB) {
threadPolicy.detectCustomSlowCalls();
}
if (targetSdk >= VERSION_CODES.M) {
threadPolicy.detectResourceMismatches();
}
if (targetSdk >= VERSION_CODES.O) {
threadPolicy.detectUnbufferedIo();
}
// -- end detectAll()
threadPolicy.penaltyLog(); threadPolicy.penaltyLog();
StrictMode.setThreadPolicy(threadPolicy.build()); StrictMode.setThreadPolicy(threadPolicy.build());
VmPolicy.Builder vmPolicy = new VmPolicy.Builder(); VmPolicy.Builder vmPolicy = new VmPolicy.Builder();
vmPolicy.detectAll();
// pulled in from vmPolicy.detectAll();
vmPolicy.detectLeakedSqlLiteObjects();
if (targetSdk >= VERSION_CODES.HONEYCOMB) {
vmPolicy.detectActivityLeaks();
vmPolicy.detectLeakedClosableObjects();
}
if (targetSdk >= VERSION_CODES.JELLY_BEAN) {
vmPolicy.detectLeakedRegistrationObjects();
}
if (targetSdk >= VERSION_CODES.JELLY_BEAN_MR2) {
vmPolicy.detectFileUriExposure();
}
if (targetSdk >= VERSION_CODES.M) {
vmPolicy.detectCleartextNetwork();
}
if (targetSdk >= VERSION_CODES.O) {
vmPolicy.detectContentUriWithoutPermission();
// do not detect untagged sockets
// vmPolicy.detectUntaggedSockets();
}
if (targetSdk >= VERSION_CODES.Q) {
vmPolicy.detectCredentialProtectedWhileLocked();
}
if (targetSdk >= VERSION_CODES.R) {
// for some reason this won't compile
// vmPolicy.detectIncorrectContextUse();
}
// -- end detectAll()
// TODO: add detectUnsafeIntentLaunch() after upgrading to API level 31
vmPolicy.penaltyLog(); vmPolicy.penaltyLog();
StrictMode.setVmPolicy(vmPolicy.build()); StrictMode.setVmPolicy(vmPolicy.build());
} }

View File

@@ -156,11 +156,8 @@ public class BriarService extends Service {
if (result == SUCCESS) { if (result == SUCCESS) {
started = true; started = true;
} else if (result == ALREADY_RUNNING) { } else if (result == ALREADY_RUNNING) {
LOG.warning("Already running"); LOG.info("Already running");
// The core has outlived the original BriarService stopSelf();
// instance. We don't know how to recover from this
// unexpected state, so try to exit cleanly
shutdownFromBackground();
} else { } else {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.warning("Startup failed: " + result); LOG.warning("Startup failed: " + result);

View File

@@ -7,8 +7,6 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.view.TrustIndicatorView;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -28,10 +26,6 @@ public class ContactItemViewHolder<I extends ContactItem>
protected final TextView name; protected final TextView name;
@Nullable @Nullable
protected final ImageView bulb; protected final ImageView bulb;
@Nullable
protected final TrustIndicatorView trustIndicator;
@Nullable
protected final TextView trustIndicatorDescription;
public ContactItemViewHolder(View v) { public ContactItemViewHolder(View v) {
super(v); super(v);
@@ -41,11 +35,6 @@ public class ContactItemViewHolder<I extends ContactItem>
name = v.findViewById(R.id.nameView); name = v.findViewById(R.id.nameView);
// this can be null as not all layouts that use this ViewHolder have it // this can be null as not all layouts that use this ViewHolder have it
bulb = v.findViewById(R.id.bulbView); bulb = v.findViewById(R.id.bulbView);
// this can be null as not all layouts that use this ViewHolder have it
trustIndicator = v.findViewById(R.id.trustIndicator);
// this can be null as not all layouts that use this ViewHolder have it
trustIndicatorDescription =
v.findViewById(R.id.trustIndicatorDescription);
} }
protected void bind(I item, @Nullable OnContactClickListener<I> listener) { protected void bind(I item, @Nullable OnContactClickListener<I> listener) {
@@ -61,29 +50,6 @@ public class ContactItemViewHolder<I extends ContactItem>
} }
} }
if (trustIndicator != null && trustIndicatorDescription != null) {
final AuthorInfo.Status status = item.getAuthorInfo().getStatus();
trustIndicator.setTrustLevel(status);
switch (status) {
case UNVERIFIED:
trustIndicatorDescription.setText(
R.string.peer_trust_level_unverified);
break;
case VERIFIED:
trustIndicatorDescription.setText(
R.string.peer_trust_level_verified);
break;
case OURSELVES:
trustIndicatorDescription.setText(
R.string.peer_trust_level_ourselves);
break;
default:
trustIndicatorDescription.setText(
R.string.peer_trust_level_stranger);
}
}
layout.setOnClickListener(v -> { layout.setOnClickListener(v -> {
if (listener != null) listener.onItemClick(avatar, item); if (listener != null) listener.onItemClick(avatar, item);
}); });

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.mailbox;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -24,7 +23,6 @@ import androidx.lifecycle.ViewModelProvider;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.util.UiUtils.showFragment; import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -217,8 +215,6 @@ public class MailboxActivity extends BriarActivity {
dialog -> supportFinishAfterTransition()); dialog -> supportFinishAfterTransition());
builder.show(); builder.show();
} else { } else {
Toast.makeText(this, R.string.mailbox_status_unlink_success,
LENGTH_LONG).show();
supportFinishAfterTransition(); supportFinishAfterTransition();
} }
} }

View File

@@ -1,8 +1,5 @@
package org.briarproject.briar.android.reporting; package org.briarproject.briar.android.reporting;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -13,7 +10,6 @@ import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -22,8 +18,6 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -33,20 +27,14 @@ import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ReportFormFragment extends BaseFragment { public class ReportFormFragment extends BaseFragment {
public final static String TAG = ReportFormFragment.class.getName(); public final static String TAG = ReportFormFragment.class.getName();
private static final Logger LOG = getLogger(TAG);
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -55,7 +43,6 @@ public class ReportFormFragment extends BaseFragment {
private EditText userCommentView; private EditText userCommentView;
private EditText userEmailView; private EditText userEmailView;
private TextView privacyPolicy;
private CheckBox includeDebugReport; private CheckBox includeDebugReport;
private Button chevron; private Button chevron;
private RecyclerView list; private RecyclerView list;
@@ -86,7 +73,6 @@ public class ReportFormFragment extends BaseFragment {
userCommentView = v.findViewById(R.id.user_comment); userCommentView = v.findViewById(R.id.user_comment);
userEmailView = v.findViewById(R.id.user_email); userEmailView = v.findViewById(R.id.user_email);
privacyPolicy = v.findViewById(R.id.PrivacyPolicy);
includeDebugReport = v.findViewById(R.id.include_debug_report); includeDebugReport = v.findViewById(R.id.include_debug_report);
chevron = v.findViewById(R.id.chevron); chevron = v.findViewById(R.id.chevron);
list = v.findViewById(R.id.list); list = v.findViewById(R.id.list);
@@ -104,8 +90,6 @@ public class ReportFormFragment extends BaseFragment {
userCommentView.setHint(R.string.describe_crash); userCommentView.setHint(R.string.describe_crash);
} }
onSingleLinkClick(privacyPolicy, this::triggerPrivacyPolicy);
chevron.setOnClickListener(view -> { chevron.setOnClickListener(view -> {
boolean show = chevron.getText().equals(getString(R.string.show)); boolean show = chevron.getText().equals(getString(R.string.show));
viewModel.showReportData(show); viewModel.showReportData(show);
@@ -177,16 +161,4 @@ public class ReportFormFragment extends BaseFragment {
} }
} }
private void triggerPrivacyPolicy() {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://briarproject.org/privacy-policy/\\"));
try {
startActivity(i);
} catch (ActivityNotFoundException e) {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(),
R.string.error_start_activity, LENGTH_LONG).show();
}
}
} }

View File

@@ -1,100 +0,0 @@
package org.briarproject.briar.android.settings;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import java.util.logging.Logger;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class AboutFragment extends Fragment {
final static String TAG = AboutFragment.class.getName();
private static final Logger LOG = getLogger(TAG);
private TextView briarVersion;
private TextView torVersion;
private TextView briarWebsite;
private TextView briarSourceCode;
private TextView briarChangelog;
private TextView briarPrivacyPolicy;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_about, container,
false);
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.about_title);
briarVersion = requireActivity().findViewById(R.id.BriarVersion);
briarVersion.setText(
getString(R.string.briar_version, BuildConfig.VERSION_NAME));
torVersion = requireActivity().findViewById(R.id.TorVersion);
torVersion.setText(
getString(R.string.tor_version, BuildConfig.TorVersion));
briarWebsite = requireActivity().findViewById(R.id.BriarWebsite);
briarSourceCode = requireActivity().findViewById(R.id.BriarSourceCode);
briarChangelog = requireActivity().findViewById(R.id.BriarChangelog);
briarPrivacyPolicy =
requireActivity().findViewById(R.id.BriarPrivacyPolicy);
briarWebsite.setOnClickListener(View -> {
String url = "https://briarproject.org/";
goToUrl(url);
});
briarSourceCode.setOnClickListener(View -> {
String url = "https://code.briarproject.org/briar/briar";
goToUrl(url);
});
briarChangelog.setOnClickListener(View -> {
String url =
"https://code.briarproject.org/briar/briar/-/wikis/changelog";
goToUrl(url);
});
briarPrivacyPolicy.setOnClickListener(View -> {
String url =
"https://briarproject.org/privacy-policy/";
goToUrl(url);
});
}
private void goToUrl(String url) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
try {
startActivity(i);
} catch (ActivityNotFoundException e) {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(),
R.string.error_start_activity, LENGTH_LONG).show();
}
}
}

View File

@@ -28,6 +28,9 @@ public class TrustIndicatorView extends AppCompatImageView {
public void setTrustLevel(Status status) { public void setTrustLevel(Status status) {
int res; int res;
switch (status) { switch (status) {
case ANONYMOUS:
res = R.drawable.trust_indicator_anonymous;
break;
case UNVERIFIED: case UNVERIFIED:
res = R.drawable.trust_indicator_unverified; res = R.drawable.trust_indicator_unverified;
break; break;

View File

@@ -1,18 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group
android:scaleX="1.104"
android:scaleY="1.104"
android:translateX="-1.248"
android:translateY="-1.248">
<path
android:pathData="m5.078,2c-0.525,0 -1.028,0.211 -1.399,0.585C3.309,2.959 3.1,3.466 3.1,3.996V16.768c0,0.529 0.209,1.038 0.579,1.412 0.371,0.374 0.874,0.584 1.399,0.584H15.851l3.589,3.026C20.019,22.278 20.9,21.862 20.9,21.1V3.996C20.9,3.466 20.691,2.959 20.321,2.585 19.95,2.211 19.446,2 18.922,2ZM6.265,5.193H17.735v5.65h-4.007v1.954h1.466c0.264,0 0.397,0.323 0.21,0.511l-3.16,3.188c-0.116,0.117 -0.304,0.117 -0.42,0L8.664,13.308C8.477,13.12 8.609,12.797 8.873,12.797H10.339V10.843H6.265Z"
android:strokeWidth="0.590769"
android:fillColor="#ffffff"
android:strokeColor="#00000000" />
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

View File

@@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/textColorPrimary"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
tools:ignore="NewApi">
<path
android:fillColor="#FF000000"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z" />
</vector>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="31dp"
android:height="12dp"
android:viewportHeight="20"
android:viewportWidth="49">
<path
android:fillColor="#a7a7a7"
android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194
L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959
L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959
L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194
L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797
L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797
L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797
L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194
L10.5752,8.38194 L10.5752,11.8208 Z"/>
<path
android:fillColor="#a7a7a7"
android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194
L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959
L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959
L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194
L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797
L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797
L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797
L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194
L26.0752,8.38194 L26.0752,11.8208 Z"/>
<path
android:fillColor="#a7a7a7"
android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194
L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959
L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959
L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194
L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797
L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797
L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797
L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194
L41.5752,8.38194 L41.5752,11.8208 Z"/>
</vector>

View File

@@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/BriarVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/briar_version"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/TorVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tor_version"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/BriarVersion" />
<TextView
android:id="@+id/LinksTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/links"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/TorVersion" />
<TextView
android:id="@+id/BriarWebsite"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/briar_website"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/LinksTitle" />
<TextView
android:id="@+id/BriarSourceCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/briar_source_code"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/BriarWebsite" />
<TextView
android:id="@+id/BriarChangelog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/briar_changelog"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/BriarSourceCode" />
<TextView
android:id="@+id/BriarPrivacyPolicy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/briar_privacy_policy"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/BriarChangelog" />
<TextView
android:id="@+id/TranslatorThanks"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/translator_thanks"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/BriarPrivacyPolicy" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,126 +1,119 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fillViewport="true"
tools:context=".android.mailbox.MailboxActivity"> tools:context=".android.mailbox.MailboxActivity">
<androidx.constraintlayout.widget.ConstraintLayout <ImageView
android:layout_width="match_parent" android:id="@+id/imageView"
android:layout_height="wrap_content"> android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginHorizontal="16dp"
app:layout_constraintBottom_toTopOf="@+id/statusTitleView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed"
tools:ignore="ContentDescription"
tools:srcCompat="@drawable/ic_help_outline_white"
tools:tint="@color/briar_orange_500" />
<ImageView <TextView
android:id="@+id/imageView" android:id="@+id/statusTitleView"
android:layout_width="32dp" android:layout_width="wrap_content"
android:layout_height="32dp" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_margin="16dp"
app:layout_constraintBottom_toTopOf="@+id/statusTitleView" android:gravity="center"
app:layout_constraintEnd_toEndOf="parent" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintHorizontal_bias="0.5" app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintBottom_toTopOf="@+id/statusMessageView"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="0.25" app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintVertical_chainStyle="packed" tools:text="@string/mailbox_status_problem_title" />
tools:ignore="ContentDescription"
tools:srcCompat="@drawable/ic_help_outline_white"
tools:tint="@color/briar_orange_500" />
<TextView <TextView
android:id="@+id/statusTitleView" android:id="@+id/statusMessageView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="16dp"
android:gravity="center" android:layout_marginBottom="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" android:gravity="center"
app:layout_constrainedWidth="true" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/statusMessageView" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toTopOf="@+id/checkButton"
app:layout_constraintTop_toBottomOf="@+id/imageView" app:layout_constraintEnd_toEndOf="parent"
tools:text="@string/mailbox_status_problem_title" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView"
tools:text="@string/mailbox_status_mailbox_too_old_message"
tools:visibility="visible" />
<TextView <org.briarproject.briar.android.view.BriarButton
android:id="@+id/statusMessageView" android:id="@+id/checkButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp" app:buttonStyle="@style/BriarButtonFlat.Neutral"
android:gravity="center" app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone" app:layout_constraintStart_toStartOf="parent"
app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@+id/statusMessageView"
app:layout_constraintBottom_toTopOf="@+id/checkButton" app:text="@string/mailbox_status_check_button" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView"
tools:text="@string/mailbox_status_mailbox_too_old_message"
tools:visibility="visible" />
<org.briarproject.briar.android.view.BriarButton <TextView
android:id="@+id/checkButton" android:id="@+id/statusInfoView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_margin="16dp"
app:buttonStyle="@style/BriarButtonFlat.Neutral" android:gravity="center"
app:layout_constraintBottom_toTopOf="@+id/statusInfoView" app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusMessageView" app:layout_constraintTop_toBottomOf="@+id/checkButton"
app:text="@string/mailbox_status_check_button" /> tools:text="@string/mailbox_status_connected_info" />
<TextView <Button
android:id="@+id/statusInfoView" android:id="@+id/wizardButton"
android:layout_width="0dp" style="@style/BriarButtonFlat.Positive"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:layout_margin="16dp" android:layout_height="wrap_content"
android:gravity="center" android:layout_margin="16dp"
app:layout_constraintBottom_toTopOf="@+id/unlinkButton" android:text="@string/mailbox_error_wizard_button"
app:layout_constraintEnd_toEndOf="parent" android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:drawableTint="@color/briar_button_text_positive"
app:layout_constraintTop_toBottomOf="@+id/checkButton" app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
tools:text="@string/mailbox_status_connected_info" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusInfoView"
app:layout_constraintVertical_bias="0.0"
tools:visibility="visible" />
<Button <Button
android:id="@+id/wizardButton" android:id="@+id/unlinkButton"
style="@style/BriarButtonFlat.Positive" style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"
android:text="@string/mailbox_error_wizard_button" android:text="@string/mailbox_status_unlink_button"
android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent"
app:drawableTint="@color/briar_button_text_positive" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/unlinkButton" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusInfoView"
app:layout_constraintVertical_bias="0.0"
tools:visibility="visible" />
<Button <ProgressBar
android:id="@+id/unlinkButton" android:id="@+id/unlinkProgress"
style="@style/BriarButtonFlat.Negative" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:visibility="invisible"
android:layout_margin="16dp" app:layout_constraintBottom_toBottomOf="@+id/unlinkButton"
android:text="@string/mailbox_status_unlink_button" app:layout_constraintEnd_toEndOf="@+id/unlinkButton"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@+id/unlinkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/unlinkButton"
app:layout_constraintStart_toStartOf="parent" /> tools:visibility="visible" />
<ProgressBar </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/unlinkProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/unlinkButton"
app:layout_constraintEnd_toEndOf="@+id/unlinkButton"
app:layout_constraintStart_toStartOf="@+id/unlinkButton"
app:layout_constraintTop_toTopOf="@+id/unlinkButton"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

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