mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
126 Commits
beta-1.4.1
...
beta-1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
559138c5b6 | ||
|
|
f90aef7767 | ||
|
|
ee417fc8d2 | ||
|
|
b424d6f98e | ||
|
|
32205ca6d3 | ||
|
|
dd3a9aa71b | ||
|
|
4e59836dd0 | ||
|
|
264b2ca2f3 | ||
|
|
23f5de66a8 | ||
|
|
79aa42c0f8 | ||
|
|
f00c3a47f5 | ||
|
|
04011e50bc | ||
|
|
7d20a844ff | ||
|
|
43581cc339 | ||
|
|
34815eb1a5 | ||
|
|
13d9e93758 | ||
|
|
98c1dca602 | ||
|
|
5ceba8f508 | ||
|
|
8e5ec347f2 | ||
|
|
f3afcb8469 | ||
|
|
3a317a9144 | ||
|
|
480a4b5901 | ||
|
|
6d9a241820 | ||
|
|
1c656d217c | ||
|
|
a503aa6ed2 | ||
|
|
85361b0099 | ||
|
|
4efdb7b75b | ||
|
|
787200d03f | ||
|
|
3ac05e4b88 | ||
|
|
7aafbdd715 | ||
|
|
617a6db84c | ||
|
|
2c295fb096 | ||
|
|
4af895d124 | ||
|
|
3cd388decd | ||
|
|
08551d16cd | ||
|
|
d905cb6cda | ||
|
|
bcc7a4b93b | ||
|
|
4fe9fa3315 | ||
|
|
079ef5b3c0 | ||
|
|
de76986ee4 | ||
|
|
96630e1b34 | ||
|
|
4eddf625d8 | ||
|
|
28ad66a03d | ||
|
|
0af371d026 | ||
|
|
a57c784b47 | ||
|
|
ab360e1e25 | ||
|
|
2aa39e43ef | ||
|
|
efb294de53 | ||
|
|
99755619c5 | ||
|
|
9990fb3b8f | ||
|
|
6d26db3d66 | ||
|
|
51301968a5 | ||
|
|
feb1c1b655 | ||
|
|
148f61a6b5 | ||
|
|
24d4debde0 | ||
|
|
a1f25c8101 | ||
|
|
62883b4bde | ||
|
|
42243f73f4 | ||
|
|
f4365330cb | ||
|
|
d3a06cf2c0 | ||
|
|
15d29f6189 | ||
|
|
339e4daded | ||
|
|
217a6dbf1c | ||
|
|
46352f664c | ||
|
|
dfcd626081 | ||
|
|
347895f6b2 | ||
|
|
7a6d075984 | ||
|
|
68ab3b0e97 | ||
|
|
16fc4f4527 | ||
|
|
8657216345 | ||
|
|
42e2926d61 | ||
|
|
a261b8e739 | ||
|
|
1699d6b5f8 | ||
|
|
848872a803 | ||
|
|
04ed3a652a | ||
|
|
d20457f338 | ||
|
|
ab29aacce0 | ||
|
|
46bb2b8ec2 | ||
|
|
6b6880c1ff | ||
|
|
5defd500ae | ||
|
|
7a888a6114 | ||
|
|
37ff06d192 | ||
|
|
85aa21ebf6 | ||
|
|
e448699895 | ||
|
|
200f83bcfe | ||
|
|
89cce89650 | ||
|
|
8982964fbf | ||
|
|
f3a3fa0ea8 | ||
|
|
0865a06ac8 | ||
|
|
f2738c8bc4 | ||
|
|
1321f8775e | ||
|
|
9764aba47d | ||
|
|
913e5da2f5 | ||
|
|
f2ce7a386b | ||
|
|
7607b65e82 | ||
|
|
c13c2d62f5 | ||
|
|
8ea7204cf6 | ||
|
|
6ec382cfc4 | ||
|
|
ad0b28a684 | ||
|
|
0ae94e9579 | ||
|
|
57bd5789d4 | ||
|
|
f7dde1250c | ||
|
|
13d96651b4 | ||
|
|
65029982ce | ||
|
|
380921ce25 | ||
|
|
87ee8cd653 | ||
|
|
d4810a6f71 | ||
|
|
aa56aba1a5 | ||
|
|
35438dbac1 | ||
|
|
543b1178a1 | ||
|
|
7f1071f5cd | ||
|
|
e8c694fe00 | ||
|
|
b58b0c74a9 | ||
|
|
9e5029917e | ||
|
|
12ca74f86a | ||
|
|
622683f45e | ||
|
|
e66f92f27e | ||
|
|
44acda2045 | ||
|
|
afd92dd916 | ||
|
|
2a969f8e0b | ||
|
|
ddc6606ccf | ||
|
|
a19a4f36c6 | ||
|
|
6765de992d | ||
|
|
b24a18b231 | ||
|
|
8e83743dd7 | ||
|
|
6a91d18003 |
@@ -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: 3h
|
timeout: 4h
|
||||||
|
|
||||||
mailbox integration test:
|
mailbox integration test:
|
||||||
extends: .optional_tests
|
extends: .optional_tests
|
||||||
|
|||||||
10
CONTRIBUTING.md
Normal file
10
CONTRIBUTING.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Folder-Description:
|
||||||
|
===================
|
||||||
|
* `briar-*`: Specifically for the Briar app (Phone/Desktop/Headless)
|
||||||
|
* `bramble-*`: The protocol stack - not necessarily Briar-dependent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* `*-api`: public stuff that can be referenced from other packages and modules - mostly interfaces + a few utility classes
|
||||||
|
* `*-core`: implementations of api that are portable across Android/Desktop/Headless
|
||||||
|
* `*-java`: implementations of api that are specific to Desktop & Headless
|
||||||
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10410
|
versionCode 10412
|
||||||
versionName "1.4.10"
|
versionName "1.4.12"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@@ -42,8 +42,10 @@ 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"
|
||||||
|
|
||||||
|
|||||||
@@ -18,3 +18,7 @@
|
|||||||
-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 * { *; }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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;
|
||||||
@@ -18,6 +19,7 @@ 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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ 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;
|
||||||
@@ -45,13 +47,14 @@ 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;
|
private final File torLib, obfs4Lib, snowflakeLib;
|
||||||
|
|
||||||
AndroidTorPlugin(Executor ioExecutor,
|
AndroidTorPlugin(Executor ioExecutor,
|
||||||
Executor wakefulIoExecutor,
|
Executor wakefulIoExecutor,
|
||||||
@@ -83,6 +86,7 @@ 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
|
||||||
@@ -108,6 +112,12 @@ 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();
|
||||||
@@ -124,39 +134,43 @@ 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 {
|
||||||
File extracted = super.getTorExecutableFile();
|
installExecutable(super.getTorExecutableFile(), torLib, TOR_LIB_NAME);
|
||||||
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 {
|
||||||
File extracted = super.getObfs4ExecutableFile();
|
installExecutable(super.getObfs4ExecutableFile(), obfs4Lib,
|
||||||
if (obfs4Lib.exists()) {
|
OBFS4_LIB_NAME);
|
||||||
// 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 obfs4 binary");
|
if (extracted.delete()) LOG.info("Deleted old binary");
|
||||||
else LOG.info("Failed to delete obfs4 binary");
|
else LOG.info("Failed to delete old 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(OBFS4_LIB_NAME, extracted);
|
extractLibraryFromApk(libName, 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(obfs4Lib.getAbsolutePath());
|
throw new FileNotFoundException(lib.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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',
|
||||||
@@ -87,8 +88,9 @@ 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.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89',
|
'org.briarproject:obfs4proxy-android:0.0.14:obfs4proxy-android-0.0.14.jar:ad9b1ee4757b05867a19e993147bbb018bddd1f26ce3da746d5f037d5991a8c8',
|
||||||
'org.briarproject:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9',
|
'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293',
|
||||||
|
'org.briarproject:tor-android:0.4.5.14:tor-android-0.4.5.14.jar:7cf1beaa6c1db51fc8fac263aba9624ef289c3db29772509efcbc59f7057330a',
|
||||||
'org.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',
|
||||||
@@ -129,10 +131,12 @@ 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',
|
||||||
|
|||||||
@@ -283,6 +283,13 @@ 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/>
|
||||||
@@ -349,13 +356,13 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Metadata query) throws DbException;
|
Metadata query) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of some messages received from the given contact that
|
* Returns the IDs of all messages received from the given contact that
|
||||||
* need to be acknowledged, up to the given number of messages.
|
* need to be acknowledged.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
|
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c)
|
||||||
int maxMessages) throws DbException;
|
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
|
||||||
@@ -492,6 +499,8 @@ 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
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
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;
|
||||||
|
|
||||||
class MailboxHelper {
|
@NotNullByDefault
|
||||||
|
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.
|
||||||
*/
|
*/
|
||||||
static int getHighestCommonMajorVersion(
|
public 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) {
|
||||||
@@ -32,4 +35,13 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -75,4 +76,23 @@ 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,6 @@ 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;
|
||||||
|
|
||||||
@@ -49,20 +46,28 @@ 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, String ownOnion,
|
void mailboxPaired(Transaction txn, MailboxProperties p)
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ 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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ 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>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when the first mailbox update is sent to a
|
||||||
|
* newly added contact, which happens in the same transaction in which the
|
||||||
|
* contact is added.
|
||||||
|
* <p>
|
||||||
|
* This event is not broadcast when the first mailbox update is sent to an
|
||||||
|
* existing contact when setting up the
|
||||||
|
* {@link MailboxUpdateManager mailbox update client}.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxUpdateSentToNewContactEvent extends Event {
|
||||||
|
|
||||||
|
private final ContactId contactId;
|
||||||
|
private final MailboxUpdate mailboxUpdate;
|
||||||
|
|
||||||
|
public MailboxUpdateSentToNewContactEvent(ContactId contactId,
|
||||||
|
MailboxUpdate mailboxUpdate) {
|
||||||
|
this.contactId = contactId;
|
||||||
|
this.mailboxUpdate = mailboxUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactId getContactId() {
|
||||||
|
return contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxUpdate getMailboxUpdate() {
|
||||||
|
return mailboxUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,12 +19,32 @@ 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) {
|
public MessageSharedEvent(MessageId message, GroupId groupId,
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,6 +338,17 @@ 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")
|
||||||
|
|||||||
@@ -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.4'
|
implementation 'org.briarproject:jtorctl:0.5'
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ 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;
|
||||||
@@ -474,6 +475,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,6 +320,13 @@ 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/>
|
||||||
@@ -345,8 +352,11 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of all contacts to which the given group's visibility is
|
* Returns the IDs of all contacts for which the given group's visibility
|
||||||
* either {@link Visibility VISIBLE} or {@link Visibility SHARED}.
|
* 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}.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -287,7 +287,12 @@ 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) transaction.attach(new MessageSharedEvent(m.getId()));
|
if (shared) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
@@ -550,6 +555,15 @@ 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 {
|
||||||
@@ -620,11 +634,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
public Collection<MessageId> getMessagesToAck(Transaction transaction,
|
||||||
ContactId c, int maxMessages) throws DbException {
|
ContactId c) 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, maxMessages);
|
return db.getMessagesToAck(txn, c, Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -746,7 +760,9 @@ 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 (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (markAsSent && transaction.isReadOnly()) {
|
||||||
|
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();
|
||||||
@@ -1097,11 +1113,7 @@ 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();
|
||||||
List<MessageId> visible = new ArrayList<>(acked.size());
|
db.lowerAckFlag(txn, c, acked);
|
||||||
for (MessageId m : acked) {
|
|
||||||
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
|
|
||||||
}
|
|
||||||
db.lowerAckFlag(txn, c, visible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1184,7 +1196,9 @@ 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);
|
||||||
transaction.attach(new MessageSharedEvent(m));
|
GroupId g = db.getGroupId(txn, m);
|
||||||
|
Map<ContactId, Boolean> visibility = db.getGroupVisibility(txn, g);
|
||||||
|
transaction.attach(new MessageSharedEvent(m, g, visibility));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1683,6 +1683,27 @@ 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 {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.feed;
|
package org.briarproject.bramble.io;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
package org.briarproject.briar.feed;
|
package org.briarproject.bramble.io;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
@@ -9,6 +11,7 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import okhttp3.Dns;
|
import okhttp3.Dns;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
class NoDns implements Dns {
|
class NoDns implements Dns {
|
||||||
|
|
||||||
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
||||||
@@ -10,7 +10,6 @@ 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;
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ 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) {
|
||||||
@@ -57,6 +55,10 @@ 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
|
||||||
|
|||||||
@@ -5,14 +5,23 @@ 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);
|
||||||
@@ -23,7 +32,9 @@ 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());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,73 +1,25 @@
|
|||||||
package org.briarproject.bramble.mailbox;
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Cancellable;
|
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.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.logging.Level.INFO;
|
import static java.util.Collections.emptyList;
|
||||||
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 implements MailboxWorker,
|
class ContactMailboxDownloadWorker extends MailboxDownloadWorker {
|
||||||
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,
|
||||||
@@ -76,57 +28,14 @@ class ContactMailboxDownloadWorker implements MailboxWorker,
|
|||||||
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
|
||||||
public void start() {
|
protected ApiCall createApiCallForDownloadCycle() {
|
||||||
LOG.info("Started");
|
return new SimpleApiCall(this::apiCallListInbox);
|
||||||
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 {
|
||||||
@@ -134,110 +43,23 @@ class ContactMailboxDownloadWorker implements MailboxWorker,
|
|||||||
if (state == State.DESTROYED) return;
|
if (state == State.DESTROYED) return;
|
||||||
}
|
}
|
||||||
LOG.info("Listing inbox");
|
LOG.info("Listing inbox");
|
||||||
List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
|
MailboxFolderId folderId =
|
||||||
requireNonNull(mailboxProperties.getInboxId()));
|
requireNonNull(mailboxProperties.getInboxId());
|
||||||
if (files.isEmpty()) onDownloadCycleFinished();
|
List<MailboxFile> files;
|
||||||
else downloadNextFile(new LinkedList<>(files));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onDownloadCycleFinished() {
|
|
||||||
boolean addObserver = false;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DOWNLOAD_CYCLE_1) {
|
|
||||||
LOG.info("First download cycle finished");
|
|
||||||
state = State.WAITING_FOR_TOR;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
mailboxApi.getFile(mailboxProperties,
|
files = mailboxApi.getFiles(mailboxProperties, folderId);
|
||||||
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 (TolerableFailureException e) {
|
||||||
// Catch this so we can continue to the next file
|
LOG.warning("Inbox folder does not exist");
|
||||||
logException(LOG, INFO, e);
|
files = emptyList();
|
||||||
}
|
}
|
||||||
if (queue.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
// List the inbox again to check for files that may have arrived
|
onDownloadCycleFinished();
|
||||||
// while we were downloading
|
|
||||||
synchronized (lock) {
|
|
||||||
if (state == State.DESTROYED) return;
|
|
||||||
apiCall = mailboxApiCaller.retryWithBackoff(
|
|
||||||
new SimpleApiCall(this::apiCallListInbox));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
Queue<FolderFile> queue = new LinkedList<>();
|
||||||
|
for (MailboxFile file : files) {
|
||||||
|
queue.add(new FolderFile(folderId, file.name));
|
||||||
|
}
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,9 +91,13 @@ 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) throws IOException, ApiException;
|
MailboxFolderId folderId)
|
||||||
|
throws IOException, ApiException, TolerableFailureException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by owner and contacts to retrieve a file.
|
* Used by owner and contacts to retrieve a file.
|
||||||
@@ -102,17 +106,22 @@ 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) throws IOException, ApiException;
|
MailboxFileId fileId, File file)
|
||||||
|
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 on 404 response,
|
* @throws TolerableFailureException if response code is 404 (folder does
|
||||||
* because file was most likely deleted already.
|
* not exist, client is not authorised to download from folder, or file
|
||||||
|
* does not exist)
|
||||||
*/
|
*/
|
||||||
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId)
|
MailboxFileId fileId)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ 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;
|
||||||
@@ -35,6 +34,7 @@ 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,6 +125,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,9 +218,11 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MailboxFile> getFiles(MailboxProperties properties,
|
public List<MailboxFile> getFiles(MailboxProperties properties,
|
||||||
MailboxFolderId folderId) throws IOException, ApiException {
|
MailboxFolderId folderId)
|
||||||
|
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();
|
||||||
@@ -243,7 +247,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));
|
||||||
}
|
}
|
||||||
Collections.sort(list);
|
sort(list);
|
||||||
return list;
|
return list;
|
||||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||||
throw new ApiException();
|
throw new ApiException();
|
||||||
@@ -252,9 +256,11 @@ class MailboxApiImpl implements MailboxApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||||
MailboxFileId fileId, File file) throws IOException, ApiException {
|
MailboxFileId fileId, File file)
|
||||||
|
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();
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ 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);
|
||||||
@@ -35,6 +39,11 @@ 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);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,667 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,17 +4,22 @@ 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;
|
||||||
@@ -37,6 +42,8 @@ public class MailboxModule {
|
|||||||
MailboxUpdateManager mailboxUpdateManager;
|
MailboxUpdateManager mailboxUpdateManager;
|
||||||
@Inject
|
@Inject
|
||||||
MailboxFileManager mailboxFileManager;
|
MailboxFileManager mailboxFileManager;
|
||||||
|
@Inject
|
||||||
|
MailboxClientManager mailboxClientManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -126,4 +133,49 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ 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.
|
||||||
|
|||||||
@@ -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.getOnion(), p.getServerSupports());
|
hook.mailboxPaired(txn, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +89,10 @@ 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);
|
||||||
@@ -112,42 +116,39 @@ 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,
|
||||||
@Nullable List<MailboxVersion> versions) throws DbException {
|
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();
|
||||||
// fetch version that the server supports first
|
// record the successful connection
|
||||||
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, serverSupports);
|
MailboxStatus status = new MailboxStatus(now, now, 0, versions);
|
||||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recordFailedConnectionAttempt(Transaction txn, long now)
|
public void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
|
// if we no longer have a paired mailbox, return
|
||||||
Settings oldSettings =
|
Settings oldSettings =
|
||||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
String onion = oldSettings.get(SETTINGS_KEY_ONION);
|
||||||
|
String token = oldSettings.get(SETTINGS_KEY_TOKEN);
|
||||||
|
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return;
|
||||||
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
|
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
|
||||||
Settings newSettings = new Settings();
|
Settings newSettings = new Settings();
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ 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;
|
||||||
@@ -38,6 +41,7 @@ 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;
|
||||||
@@ -45,6 +49,9 @@ 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
|
||||||
@@ -116,7 +123,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);
|
addingContact(txn, c, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +139,17 @@ 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);
|
||||||
@@ -143,13 +161,17 @@ 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
|
||||||
createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
|
u = createAndSendUpdateWithMailbox(txn, c,
|
||||||
ownProps.getOnion());
|
ownProps.getServerSupports(), 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
|
||||||
sendUpdateNoMailbox(txn, c);
|
u = sendUpdateNoMailbox(txn, c);
|
||||||
|
}
|
||||||
|
if (attachEvent) {
|
||||||
|
txn.attach(new MailboxUpdateSentToNewContactEvent(c.getId(), u));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,18 +181,103 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mailboxPaired(Transaction txn, String ownOnion,
|
public void mailboxPaired(Transaction txn, MailboxProperties p)
|
||||||
List<MailboxVersion> serverSupports) throws DbException {
|
throws DbException {
|
||||||
|
Map<ContactId, MailboxUpdateWithMailbox> localUpdates = new HashMap<>();
|
||||||
for (Contact c : db.getContacts(txn)) {
|
for (Contact c : db.getContacts(txn)) {
|
||||||
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
|
MailboxUpdateWithMailbox u = createAndSendUpdateWithMailbox(txn, c,
|
||||||
|
p.getServerSupports(), p.getOnion());
|
||||||
|
localUpdates.put(c.getId(), u);
|
||||||
|
}
|
||||||
|
txn.attach(new MailboxPairedEvent(p, localUpdates));
|
||||||
|
// Store the list of server-supported versions
|
||||||
|
try {
|
||||||
|
storeSentServerSupports(txn, p.getServerSupports());
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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)) {
|
||||||
sendUpdateNoMailbox(txn, c);
|
MailboxUpdate u = 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
|
||||||
@@ -239,18 +346,19 @@ class MailboxUpdateManagerImpl implements MailboxUpdateManager,
|
|||||||
* supported Mailbox API version(s). All of which the contact needs to
|
* supported Mailbox API version(s). All of which the contact needs to
|
||||||
* communicate with our Mailbox.
|
* communicate with our Mailbox.
|
||||||
*/
|
*/
|
||||||
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
|
private MailboxUpdateWithMailbox createAndSendUpdateWithMailbox(
|
||||||
List<MailboxVersion> serverSupports, String ownOnion)
|
Transaction txn, Contact c, List<MailboxVersion> serverSupports,
|
||||||
throws DbException {
|
String ownOnion) 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()));
|
||||||
MailboxUpdate u =
|
MailboxUpdateWithMailbox 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -259,11 +367,12 @@ 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 void sendUpdateNoMailbox(Transaction txn, Contact c)
|
private MailboxUpdate 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
|
||||||
@@ -288,21 +397,27 @@ 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);
|
||||||
long version = latest == null ? 1 : latest.version + 1;
|
storeMessageReplaceLatest(txn, g, u, latest);
|
||||||
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 {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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;
|
||||||
@@ -12,6 +13,8 @@ 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;
|
||||||
@@ -32,6 +35,7 @@ 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;
|
||||||
@@ -58,9 +62,16 @@ 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,
|
||||||
@@ -95,6 +106,7 @@ 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;
|
||||||
@@ -121,6 +133,7 @@ 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,
|
||||||
@@ -133,6 +146,7 @@ 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;
|
||||||
@@ -182,6 +196,12 @@ 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 {
|
||||||
@@ -328,13 +348,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,8 +384,14 @@ class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
|
|||||||
onDataToSend();
|
onDataToSend();
|
||||||
}
|
}
|
||||||
} else if (e instanceof MessageSharedEvent) {
|
} else if (e instanceof MessageSharedEvent) {
|
||||||
LOG.info("Message shared");
|
MessageSharedEvent m = (MessageSharedEvent) e;
|
||||||
onDataToSend();
|
// If the contact is present in the map (ie the value is not null)
|
||||||
|
// and the value is true, the message's group is shared with the
|
||||||
|
// contact and therefore the message may now be sendable
|
||||||
|
if (m.getGroupVisibility().get(contactId) == TRUE) {
|
||||||
|
LOG.info("Message shared");
|
||||||
|
onDataToSend();
|
||||||
|
}
|
||||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||||
if (g.getVisibility() == SHARED &&
|
if (g.getVisibility() == SHARED &&
|
||||||
@@ -373,6 +399,18 @@ 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,4 +429,36 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ interface MailboxWorkerFactory {
|
|||||||
ConnectivityChecker connectivityChecker,
|
ConnectivityChecker connectivityChecker,
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
MailboxProperties properties);
|
MailboxProperties properties);
|
||||||
|
|
||||||
|
MailboxWorker createContactListWorkerForOwnMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxProperties properties);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
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;
|
||||||
@@ -24,9 +26,11 @@ 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,
|
||||||
@@ -34,17 +38,21 @@ 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
|
||||||
@@ -53,9 +61,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, connectivityChecker,
|
clock, taskScheduler, eventBus, connectionRegistry,
|
||||||
mailboxApiCaller, mailboxApi, mailboxFileManager,
|
connectivityChecker, mailboxApiCaller, mailboxApi,
|
||||||
properties, folderId, contactId);
|
mailboxFileManager, properties, folderId, contactId);
|
||||||
eventBus.addListener(worker);
|
eventBus.addListener(worker);
|
||||||
return worker;
|
return worker;
|
||||||
}
|
}
|
||||||
@@ -75,7 +83,19 @@ class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
|
|||||||
ConnectivityChecker connectivityChecker,
|
ConnectivityChecker connectivityChecker,
|
||||||
TorReachabilityMonitor reachabilityMonitor,
|
TorReachabilityMonitor reachabilityMonitor,
|
||||||
MailboxProperties properties) {
|
MailboxProperties properties) {
|
||||||
// TODO
|
return new OwnMailboxDownloadWorker(connectivityChecker,
|
||||||
throw new UnsupportedOperationException();
|
reachabilityMonitor, mailboxApiCaller, mailboxApi,
|
||||||
|
mailboxFileManager, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MailboxWorker createContactListWorkerForOwnMailbox(
|
||||||
|
ConnectivityChecker connectivityChecker,
|
||||||
|
MailboxProperties properties) {
|
||||||
|
OwnMailboxContactListWorker worker = new OwnMailboxContactListWorker(
|
||||||
|
ioExecutor, db, eventBus, connectivityChecker, mailboxApiCaller,
|
||||||
|
mailboxApi, mailboxUpdateManager, properties);
|
||||||
|
eventBus.addListener(worker);
|
||||||
|
return worker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ 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;
|
||||||
@@ -30,6 +31,7 @@ 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,
|
||||||
@@ -57,6 +59,7 @@ 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");
|
||||||
|
|||||||
@@ -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
|
||||||
logException(LOG, INFO, e);
|
LOG.warning("Contact does not exist");
|
||||||
}
|
}
|
||||||
updateContactList();
|
updateContactList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,8 @@ public interface CircumventionProvider {
|
|||||||
DEFAULT_OBFS4,
|
DEFAULT_OBFS4,
|
||||||
NON_DEFAULT_OBFS4,
|
NON_DEFAULT_OBFS4,
|
||||||
VANILLA,
|
VANILLA,
|
||||||
MEEK
|
MEEK,
|
||||||
|
SNOWFLAKE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,13 +42,14 @@ 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", "TM"};
|
String[] NON_DEFAULT_BRIDGES = {"BY", "RU"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Countries where vanilla bridges are blocked via DPI but non-default
|
* Countries where vanilla bridges are blocked via DPI but non-default
|
||||||
* obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}.
|
* obfs4 bridges, meek and snowflake may work. Should be a subset of
|
||||||
|
* {@link #BRIDGES}.
|
||||||
*/
|
*/
|
||||||
String[] DPI_BRIDGES = {"CN", "IR"};
|
String[] DPI_BRIDGES = {"CN", "IR", "TM"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if vanilla Tor connections are blocked in the given country.
|
* Returns true if vanilla Tor connections are blocked in the given country.
|
||||||
@@ -68,6 +70,6 @@ public interface CircumventionProvider {
|
|||||||
List<BridgeType> getSuitableBridgeTypes(String countryCode);
|
List<BridgeType> getSuitableBridgeTypes(String countryCode);
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
List<String> getBridges(BridgeType type);
|
List<String> getBridges(BridgeType type, String countryCode,
|
||||||
|
boolean letsEncrypt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ 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;
|
||||||
@@ -18,6 +20,7 @@ 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
|
||||||
@@ -25,6 +28,8 @@ 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));
|
||||||
@@ -58,7 +63,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);
|
return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE);
|
||||||
} else {
|
} else {
|
||||||
return asList(DEFAULT_OBFS4, VANILLA);
|
return asList(DEFAULT_OBFS4, VANILLA);
|
||||||
}
|
}
|
||||||
@@ -66,7 +71,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
public List<String> getBridges(BridgeType type) {
|
public List<String> getBridges(BridgeType type, String countryCode,
|
||||||
|
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);
|
||||||
@@ -79,10 +85,45 @@ 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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ 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.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.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;
|
||||||
@@ -212,6 +213,10 @@ 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;
|
||||||
@@ -338,6 +343,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
geoIpFile.delete();
|
geoIpFile.delete();
|
||||||
installTorExecutable();
|
installTorExecutable();
|
||||||
installObfs4Executable();
|
installObfs4Executable();
|
||||||
|
installSnowflakeExecutable();
|
||||||
if (!doneFile.createNewFile())
|
if (!doneFile.createNewFile())
|
||||||
LOG.warning("Failed to create done file");
|
LOG.warning("Failed to create done file");
|
||||||
}
|
}
|
||||||
@@ -363,17 +369,29 @@ 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 {
|
||||||
InputStream in = resourceProvider
|
return getZipInputStream("tor");
|
||||||
.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("obfs4proxy_" + architecture, ".zip");
|
.getResourceInputStream(basename + "_" + 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;
|
||||||
@@ -402,6 +420,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
|
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
|
||||||
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
|
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
|
||||||
append(strb, "ClientTransportPlugin meek_lite 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")));
|
||||||
@@ -559,7 +579,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableBridges(List<BridgeType> bridgeTypes)
|
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
|
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
|
||||||
try {
|
try {
|
||||||
@@ -569,8 +589,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
} else {
|
} else {
|
||||||
Collection<String> conf = new ArrayList<>();
|
Collection<String> conf = new ArrayList<>();
|
||||||
conf.add("UseBridges 1");
|
conf.add("UseBridges 1");
|
||||||
|
boolean letsEncrypt = canVerifyLetsEncryptCerts();
|
||||||
for (BridgeType bridgeType : bridgeTypes) {
|
for (BridgeType bridgeType : bridgeTypes) {
|
||||||
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
conf.addAll(circumventionProvider
|
||||||
|
.getBridges(bridgeType, countryCode, letsEncrypt));
|
||||||
}
|
}
|
||||||
controlConnection.setConf(conf);
|
controlConnection.setConf(conf);
|
||||||
}
|
}
|
||||||
@@ -579,6 +601,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
@@ -800,6 +831,13 @@ 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("[^ ]+ ", "");
|
||||||
}
|
}
|
||||||
@@ -937,7 +975,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
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);
|
||||||
} else {
|
} else {
|
||||||
bridgeTypes = circumventionProvider
|
bridgeTypes = circumventionProvider
|
||||||
.getSuitableBridgeTypes(country);
|
.getSuitableBridgeTypes(country);
|
||||||
@@ -961,7 +999,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (enableNetwork) {
|
if (enableNetwork) {
|
||||||
enableBridges(bridgeTypes);
|
enableBridges(bridgeTypes, country);
|
||||||
enableConnectionPadding(enableConnectionPadding);
|
enableConnectionPadding(enableConnectionPadding);
|
||||||
enableIpv6(ipv6Only);
|
enableIpv6(ipv6Only);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ 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;
|
||||||
@@ -233,7 +234,13 @@ 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) {
|
||||||
generateOffer();
|
MessageSharedEvent m = (MessageSharedEvent) e;
|
||||||
|
// If the contact is present in the map (ie the value is not null)
|
||||||
|
// and the value is true, the message's group is shared with the
|
||||||
|
// contact and therefore the message may now be sendable
|
||||||
|
if (m.getGroupVisibility().get(contactId) == TRUE) {
|
||||||
|
generateOffer();
|
||||||
|
}
|
||||||
} 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 &&
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ 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;
|
||||||
@@ -61,26 +63,32 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void sendAcks() throws DbException, IOException {
|
void sendAcks() throws DbException, IOException {
|
||||||
while (!isInterrupted()) {
|
List<MessageId> idsToAck = loadMessageIdsToAck();
|
||||||
Collection<MessageId> idsToAck = loadMessageIdsToAck();
|
int idsSent = 0;
|
||||||
if (idsToAck.isEmpty()) break;
|
while (idsSent < idsToAck.size() && !isInterrupted()) {
|
||||||
recordWriter.writeAck(new Ack(idsToAck));
|
int idsRemaining = idsToAck.size() - idsSent;
|
||||||
sessionRecord.onAckSent(idsToAck);
|
long capacity = getRemainingCapacity();
|
||||||
|
long idCapacity =
|
||||||
|
(capacity - RECORD_HEADER_BYTES) / MessageId.LENGTH;
|
||||||
|
if (idCapacity == 0) break; // Out of capacity
|
||||||
|
int idsInRecord = (int) min(idCapacity, MAX_MESSAGE_IDS);
|
||||||
|
int idsToSend = min(idsRemaining, idsInRecord);
|
||||||
|
List<MessageId> acked =
|
||||||
|
idsToAck.subList(idsSent, idsSent + idsToSend);
|
||||||
|
recordWriter.writeAck(new Ack(acked));
|
||||||
|
sessionRecord.onAckSent(acked);
|
||||||
LOG.info("Sent ack");
|
LOG.info("Sent ack");
|
||||||
|
idsSent += idsToSend;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<MessageId> loadMessageIdsToAck() throws DbException {
|
private List<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, maxMessageIds));
|
db.getMessagesToAck(txn, contactId));
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info(ids.size() + " messages to ack");
|
LOG.info(ids.size() + " messages to ack");
|
||||||
}
|
}
|
||||||
return ids;
|
return new ArrayList<>(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getRemainingCapacity() {
|
private long getRemainingCapacity() {
|
||||||
|
|||||||
@@ -11,23 +11,26 @@ 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 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 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 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
|
n Bridge obfs4 70.34.249.113:443 F441B16ABB1055794C2CE01821FC05047B2C8CFC cert=MauLNoyq8EwjY4Qe0oASYzs2hXdSjNgy+BtP9oo1naHhRsyKTtAZzeNv08RnzWjMJrTwcg iat-mode=0
|
||||||
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
|
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
|
||||||
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
|
||||||
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
v Bridge 185.189.195.124:8199 A1F3EE78F9C2343668E68FEB84358A4C742831A5
|
||||||
|
v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB
|
||||||
|
v Bridge 75.100.92.154:22815 465E990FA8A752DDE861EDF31E42454ACC037AB4
|
||||||
|
m Bridge meek_lite 192.0.2.2:80 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||||
|
s Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72
|
||||||
|
|||||||
4
bramble-core/src/main/resources/snowflake-params
Normal file
4
bramble-core/src/main/resources/snowflake-params
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
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
|
||||||
@@ -10,6 +10,8 @@ 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;
|
||||||
|
|
||||||
@@ -20,7 +22,9 @@ 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 {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ 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;
|
||||||
@@ -283,12 +284,16 @@ 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);
|
||||||
// The message was added, so the listeners should be called
|
// Broadcast events for message being added and changing state
|
||||||
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 (shared)
|
// If message is shared, get group visibility and broadcast event
|
||||||
|
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);
|
||||||
@@ -389,7 +394,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(true, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessagesToAck(transaction, contactId, 123));
|
db.getMessagesToAck(transaction, contactId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -694,11 +699,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(15).of(database).startTransaction();
|
exactly(16).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(15).of(database).containsMessage(txn, messageId);
|
exactly(16).of(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(15).of(database).abortTransaction(txn);
|
exactly(16).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));
|
||||||
@@ -722,6 +727,14 @@ 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));
|
||||||
@@ -1396,14 +1409,7 @@ 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));
|
||||||
// First message is still visible to the contact - flag lowered
|
oneOf(database).lowerAckFlag(txn, contactId, acked);
|
||||||
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,
|
||||||
@@ -1880,12 +1886,16 @@ 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 for message added event
|
// Broadcast events for message being added and changing state
|
||||||
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 (shared)
|
// If message is shared, get group visibility and broadcast event
|
||||||
|
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()
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ 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());
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testStartAndDestroyWithNoContactsAssigned() {
|
public void testStartAndDestroyWithNoContactsAssigned() {
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
client.destroy();
|
client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +56,7 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +74,12 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
|
|||||||
client.deassignContactForUpload(contactId);
|
client.deassignContactForUpload(contactId);
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
client.destroy();
|
client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void assignContactForDownloadAndDestroyClient() {
|
public void testAssignContactForDownloadAndDestroyClient() {
|
||||||
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
|
||||||
@@ -85,11 +89,12 @@ 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 assignAndDeassignContactForDownload() {
|
public void testAssignAndDeassignContactForDownload() {
|
||||||
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
|
||||||
@@ -102,6 +107,7 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
|
|||||||
client.deassignContactForDownload(contactId);
|
client.deassignContactForDownload(contactId);
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
|
expectDestroyConnectivityChecker();
|
||||||
client.destroy();
|
client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,4 +134,10 @@ public class ContactMailboxClientTest extends BrambleMockTestCase {
|
|||||||
oneOf(worker).destroy();
|
oneOf(worker).destroy();
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void expectDestroyConnectivityChecker() {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(connectivityChecker).destroy();
|
||||||
|
}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,68 @@
|
|||||||
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 extends BrambleMockTestCase {
|
public class ContactMailboxDownloadWorkerTest
|
||||||
|
extends MailboxDownloadWorkerTest<ContactMailboxDownloadWorker> {
|
||||||
|
|
||||||
private final ConnectivityChecker connectivityChecker =
|
public ContactMailboxDownloadWorkerTest() {
|
||||||
context.mock(ConnectivityChecker.class);
|
mailboxProperties = getMailboxProperties(false, CLIENT_SUPPORTS);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
deleteTestDirectory(testDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
|
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
|
||||||
// When the worker is started it should start a connectivity check
|
// When the worker is started it should start a connectivity check
|
||||||
context.checking(new Expectations() {{
|
expectStartConnectivityCheck();
|
||||||
oneOf(connectivityChecker).checkConnectivity(
|
|
||||||
with(mailboxProperties), with(worker));
|
|
||||||
}});
|
|
||||||
|
|
||||||
worker.start();
|
worker.start();
|
||||||
|
|
||||||
// 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
|
||||||
context.checking(new Expectations() {{
|
expectRemoveObservers();
|
||||||
oneOf(connectivityChecker).removeObserver(worker);
|
worker.destroy();
|
||||||
oneOf(torReachabilityMonitor).removeObserver(worker);
|
}
|
||||||
}});
|
|
||||||
|
|
||||||
|
@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-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
|
||||||
|
// and reachability observers
|
||||||
|
expectRemoveObservers();
|
||||||
worker.destroy();
|
worker.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,150 +70,67 @@ public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
|
|||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectStartConnectivityCheck();
|
||||||
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<>();
|
||||||
context.checking(new Expectations() {{
|
expectStartTask(listTask);
|
||||||
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<>();
|
||||||
context.checking(new Expectations() {{
|
expectCheckForFiles(mailboxProperties.getInboxId(), files);
|
||||||
oneOf(mailboxApi).getFiles(mailboxProperties,
|
expectStartTask(downloadTask);
|
||||||
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<>();
|
||||||
context.checking(new Expectations() {{
|
expectDownloadFile(mailboxProperties.getInboxId(), file1);
|
||||||
oneOf(mailboxFileManager).createTempFileForDownload();
|
expectStartTask(deleteTask);
|
||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectDeleteFile(mailboxProperties.getInboxId(), file1, true);
|
||||||
oneOf(mailboxApi).deleteFile(mailboxProperties,
|
expectStartTask(downloadTask);
|
||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectDownloadFile(mailboxProperties.getInboxId(), file2);
|
||||||
oneOf(mailboxFileManager).createTempFileForDownload();
|
expectStartTask(deleteTask);
|
||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectDeleteFile(mailboxProperties.getInboxId(), file2, false);
|
||||||
oneOf(mailboxApi).deleteFile(mailboxProperties,
|
expectStartTask(listTask);
|
||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
|
||||||
oneOf(mailboxApi).getFiles(mailboxProperties,
|
expectAddReachabilityObserver();
|
||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectStartTask(listTask);
|
||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectCheckForFiles(mailboxProperties.getInboxId(), emptyList());
|
||||||
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
|
||||||
context.checking(new Expectations() {{
|
expectRemoveObservers();
|
||||||
oneOf(connectivityChecker).removeObserver(worker);
|
|
||||||
oneOf(torReachabilityMonitor).removeObserver(worker);
|
|
||||||
}});
|
|
||||||
|
|
||||||
worker.destroy();
|
worker.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -630,6 +630,7 @@ 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);
|
||||||
@@ -706,13 +707,21 @@ public class MailboxApiTest extends BrambleTestCase {
|
|||||||
assertEquals("GET", request8.getMethod());
|
assertEquals("GET", request8.getMethod());
|
||||||
assertToken(request8, token);
|
assertToken(request8, token);
|
||||||
|
|
||||||
// 500 internal server error
|
// 404 not found
|
||||||
assertThrows(ApiException.class,
|
assertThrows(TolerableFailureException.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
|
||||||
|
|||||||
@@ -0,0 +1,929 @@
|
|||||||
|
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();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
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);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.getFiles(ownerProperties, contact.inboxId));
|
api.getFiles(ownerProperties, contact.inboxId));
|
||||||
|
|
||||||
// contact downloads file
|
// contact downloads file
|
||||||
@@ -197,12 +197,13 @@ 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(ApiException.class, () -> api.getFile(ownerProperties,
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
contact.inboxId, fileName1, file1forbidden));
|
api.getFile(ownerProperties, contact.inboxId, fileName1,
|
||||||
|
file1forbidden));
|
||||||
assertEquals(0, file1forbidden.length());
|
assertEquals(0, file1forbidden.length());
|
||||||
|
|
||||||
// owner can't delete file
|
// owner can't delete file
|
||||||
assertThrows(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.deleteFile(ownerProperties, contact.inboxId, fileName1));
|
api.deleteFile(ownerProperties, contact.inboxId, fileName1));
|
||||||
|
|
||||||
// contact deletes file
|
// contact deletes file
|
||||||
@@ -232,7 +233,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(ApiException.class, () ->
|
assertThrows(TolerableFailureException.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
|
||||||
@@ -252,17 +253,19 @@ 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(ApiException.class, () -> api.getFile(contactProperties,
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
contact.outboxId, file2name, file2forbidden));
|
api.getFile(contactProperties, contact.outboxId, file2name,
|
||||||
assertThrows(ApiException.class, () -> api.getFile(contactProperties,
|
file2forbidden));
|
||||||
contact.outboxId, file3name, file3forbidden));
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
|
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(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.deleteFile(contactProperties, contact.outboxId, file2name));
|
api.deleteFile(contactProperties, contact.outboxId, file2name));
|
||||||
assertThrows(ApiException.class, () ->
|
assertThrows(TolerableFailureException.class, () ->
|
||||||
api.deleteFile(contactProperties, contact.outboxId, file3name));
|
api.deleteFile(contactProperties, contact.outboxId, file3name));
|
||||||
|
|
||||||
// owner deletes files
|
// owner deletes files
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ 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,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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;
|
||||||
@@ -18,7 +19,6 @@ 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,10 +27,12 @@ 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;
|
||||||
@@ -39,6 +41,7 @@ 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);
|
||||||
@@ -47,7 +50,10 @@ 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());
|
||||||
@@ -56,6 +62,14 @@ 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);
|
||||||
@@ -72,14 +86,10 @@ 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(settings));
|
will(returnValue(pairedSettings));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
|
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
|
||||||
@@ -93,20 +103,38 @@ 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, onion);
|
expectedSettings.put(SETTINGS_KEY_ONION, "");
|
||||||
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
|
expectedSettings.put(SETTINGS_KEY_TOKEN, "");
|
||||||
expectedSettings.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS,
|
expectedSettings.put(SETTINGS_KEY_ATTEMPTS, "");
|
||||||
serverSupportsInts);
|
expectedSettings.put(SETTINGS_KEY_LAST_ATTEMPT, "");
|
||||||
MailboxProperties properties = new MailboxProperties(onion, token,
|
expectedSettings.put(SETTINGS_KEY_LAST_SUCCESS, "");
|
||||||
serverSupports);
|
expectedSettings.put(SETTINGS_KEY_SERVER_SUPPORTS, "");
|
||||||
|
|
||||||
|
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.setOwnMailboxProperties(txn, properties);
|
manager.removeOwnMailboxProperties(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -147,63 +175,60 @@ 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(oldSettings));
|
will(returnValue(pairedSettings));
|
||||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
SETTINGS_NAMESPACE);
|
SETTINGS_NAMESPACE);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
manager.recordSuccessfulConnection(txn, now);
|
manager.recordSuccessfulConnection(txn, now, serverSupports);
|
||||||
assertTrue(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
|
OwnMailboxConnectionStatusEvent e =
|
||||||
|
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 testRecordsSuccessWithVersions() throws Exception {
|
public void testDoesNotRecordSuccessIfNotPaired() 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).mergeSettings(txn, expectedSettings,
|
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
SETTINGS_NAMESPACE);
|
will(returnValue(new Settings()));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
manager.recordSuccessfulConnection(txn, now, versions);
|
manager.recordSuccessfulConnection(txn, now, serverSupports);
|
||||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
assertFalse(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRecordsFailureOnFirstAttempt() throws Exception {
|
public void testRecordsFailureOnFirstAttempt() throws Exception {
|
||||||
testRecordsFailure(new Settings(), 0);
|
testRecordsFailure(pairedSettings, 0, 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);
|
testRecordsFailure(oldSettings, attempts, lastSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testRecordsFailure(Settings oldSettings, int oldAttempts)
|
private void testRecordsFailure(Settings oldSettings, int oldAttempts,
|
||||||
throws Exception {
|
long lastSuccess) 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);
|
||||||
@@ -217,6 +242,25 @@ 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
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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;
|
||||||
@@ -16,6 +17,9 @@ 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;
|
||||||
@@ -32,9 +36,12 @@ 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;
|
||||||
@@ -45,6 +52,7 @@ 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;
|
||||||
@@ -71,6 +79,11 @@ 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();
|
||||||
@@ -80,6 +93,8 @@ 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;
|
||||||
@@ -105,6 +120,12 @@ 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);
|
||||||
|
|
||||||
@@ -135,8 +156,6 @@ 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,
|
||||||
@@ -158,8 +177,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(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
@@ -167,9 +186,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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 1, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict);
|
emptyServerSupports, emptyPropsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
@@ -178,14 +197,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,
|
||||||
@@ -207,8 +226,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(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(ownProps));
|
will(returnValue(ownProps));
|
||||||
@@ -222,9 +241,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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 1, someClientSupports,
|
||||||
someServerSupports, propsDict);
|
someServerSupports, propsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
||||||
@@ -233,14 +252,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 txn = new Transaction(null, false);
|
Transaction txn1 = 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<>();
|
||||||
@@ -249,46 +268,48 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
someClientSupports));
|
someClientSupports));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn1, localGroup.getId());
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn1, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn1);
|
||||||
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(txn, contactGroup);
|
oneOf(db).addGroup(txn1, contactGroup);
|
||||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
oneOf(clientVersioningManager).getClientVisibility(txn1,
|
||||||
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(txn1, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn1, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn1);
|
||||||
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(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn1,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(emptyMessageMetadata));
|
will(returnValue(emptyMessageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn1, contactGroupId, 1, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict);
|
emptyServerSupports, emptyPropsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn1, localGroup.getId(),
|
||||||
sentDict);
|
sentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn1);
|
||||||
|
|
||||||
|
assertFalse(hasEvent(txn1, MailboxUpdateSentToNewContactEvent.class));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn2, localGroup.getId());
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn2,
|
||||||
localGroup.getId());
|
localGroup.getId());
|
||||||
will(returnValue(sentDict));
|
will(returnValue(sentDict));
|
||||||
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
oneOf(clientHelper).parseMailboxVersionList(someClientSupports);
|
||||||
@@ -296,16 +317,16 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
t = createInstance(someClientSupportsList);
|
t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn2);
|
||||||
|
|
||||||
|
assertFalse(hasEvent(txn2, MailboxUpdateSentToNewContactEvent.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSendsUpdateWhenClientSupportsChangedOnSecondStartup()
|
public void testSendsUpdateWhenClientSupportsChangedOnSecondStartup()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn1 = 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<>();
|
||||||
@@ -314,41 +335,43 @@ public class MailboxUpdateManagerImplTest extends BrambleMockTestCase {
|
|||||||
someClientSupports));
|
someClientSupports));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn1, localGroup.getId());
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn1, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn1);
|
||||||
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(txn, contactGroup);
|
oneOf(db).addGroup(txn1, contactGroup);
|
||||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
oneOf(clientVersioningManager).getClientVisibility(txn1,
|
||||||
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(txn1, contact.getId(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn1, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn1);
|
||||||
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(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn1,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(emptyMessageMetadata));
|
will(returnValue(emptyMessageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn1, contactGroupId, 1, someClientSupports,
|
||||||
emptyServerSupports, emptyPropsDict);
|
emptyServerSupports, emptyPropsDict);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn1, localGroup.getId(),
|
||||||
sentDict);
|
sentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
MailboxUpdateManagerImpl t = createInstance(someClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn1);
|
||||||
|
|
||||||
|
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),
|
||||||
@@ -357,66 +380,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 body = BdfList.of(1, someClientSupports, someServerSupports,
|
BdfList oldBody = BdfList.of(1, someClientSupports, emptyServerSupports,
|
||||||
propsDict);
|
emptyPropsDict);
|
||||||
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(txn, localGroup.getId());
|
oneOf(db).containsGroup(txn2, 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(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn2,
|
||||||
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(txn);
|
oneOf(db).getContacts(txn2);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn2, 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(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn2,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn2, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(oldBody));
|
||||||
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
oneOf(clientHelper).parseAndValidateMailboxUpdate(
|
||||||
someClientSupports, someServerSupports, propsDict);
|
someClientSupports, emptyServerSupports, emptyPropsDict);
|
||||||
will(returnValue(updateWithMailbox));
|
will(returnValue(updateNoMailbox));
|
||||||
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(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn2,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 2,
|
expectStoreMessage(txn2, contactGroupId, 2,
|
||||||
newerClientSupports, someServerSupports, propsDict);
|
newerClientSupports, emptyServerSupports, emptyPropsDict);
|
||||||
oneOf(db).removeMessage(txn, messageId);
|
oneOf(db).removeMessage(txn2, messageId);
|
||||||
|
|
||||||
oneOf(clientHelper).mergeGroupMetadata(txn, localGroup.getId(),
|
oneOf(clientHelper).mergeGroupMetadata(txn2, localGroup.getId(),
|
||||||
newerSentDict);
|
newerSentDict);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
t = createInstance(newerClientSupportsList);
|
t = createInstance(newerClientSupportsList);
|
||||||
t.onDatabaseOpened(txn);
|
t.onDatabaseOpened(txn2);
|
||||||
|
|
||||||
|
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() {{
|
||||||
@@ -429,8 +452,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(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
@@ -438,22 +461,24 @@ 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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 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() {{
|
||||||
@@ -466,8 +491,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(),
|
||||||
contactGroup.getId(), SHARED);
|
contactGroupId, SHARED);
|
||||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
oneOf(clientHelper).setContactId(txn, contactGroupId,
|
||||||
contact.getId());
|
contact.getId());
|
||||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||||
will(returnValue(ownProps));
|
will(returnValue(ownProps));
|
||||||
@@ -481,21 +506,23 @@ 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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 1, someClientSupports,
|
expectStoreMessage(txn, contactGroupId, 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,
|
||||||
@@ -512,9 +539,6 @@ 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();
|
||||||
@@ -549,16 +573,23 @@ 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();
|
||||||
@@ -601,14 +632,22 @@ 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),
|
||||||
@@ -635,16 +674,13 @@ 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());
|
||||||
@@ -657,37 +693,55 @@ 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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
|
// Replace latest update with new update
|
||||||
|
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.getOnion(), someServerSupportsList);
|
t.mailboxPaired(txn, ownProps);
|
||||||
|
|
||||||
|
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<>();
|
||||||
@@ -701,30 +755,117 @@ 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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
expectStoreMessage(txn, contactGroup.getId(), 2, someClientSupports,
|
// Replace latest update with new update
|
||||||
|
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)
|
||||||
@@ -742,7 +883,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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -760,8 +901,6 @@ 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<>();
|
||||||
|
|
||||||
@@ -772,7 +911,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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(emptyMessageMetadata));
|
will(returnValue(emptyMessageMetadata));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -783,8 +922,6 @@ 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)
|
||||||
@@ -802,7 +939,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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -819,8 +956,6 @@ 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)
|
||||||
@@ -838,7 +973,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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -855,8 +990,6 @@ 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)
|
||||||
@@ -874,7 +1007,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,
|
||||||
contactGroup.getId());
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
oneOf(clientHelper).getMessageAsList(txn, messageId);
|
||||||
will(returnValue(body));
|
will(returnValue(body));
|
||||||
@@ -909,4 +1042,24 @@ 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
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;
|
||||||
@@ -25,10 +29,13 @@ 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;
|
||||||
@@ -50,6 +57,8 @@ 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 =
|
||||||
@@ -72,6 +81,9 @@ 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;
|
||||||
@@ -81,8 +93,9 @@ 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, connectivityChecker, mailboxApiCaller, mailboxApi,
|
eventBus, connectionRegistry, connectivityChecker,
|
||||||
mailboxFileManager, mailboxProperties, folderId, contactId);
|
mailboxApiCaller, mailboxApi, mailboxFileManager,
|
||||||
|
mailboxProperties, folderId, contactId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -93,8 +106,11 @@ 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 for data to send
|
// 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();
|
expectRunTaskOnIoExecutor();
|
||||||
|
expectCheckConnectionRegistry(false);
|
||||||
expectCheckForDataToSendNoDataWaiting();
|
expectCheckForDataToSendNoDataWaiting();
|
||||||
|
|
||||||
worker.start();
|
worker.start();
|
||||||
@@ -106,15 +122,59 @@ 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 for data to send. As
|
// When the worker is started it should check the connection registry.
|
||||||
// there's data ready to send immediately, the worker should start a
|
// We're not connected to the contact, so the worker should check for
|
||||||
// connectivity check
|
// data to send. As there's data ready to send immediately, the worker
|
||||||
|
// should start a connectivity check
|
||||||
expectRunTaskOnIoExecutor();
|
expectRunTaskOnIoExecutor();
|
||||||
|
expectCheckConnectionRegistry(false);
|
||||||
expectCheckForDataToSendAndStartConnectivityCheck();
|
expectCheckForDataToSendAndStartConnectivityCheck();
|
||||||
|
|
||||||
worker.start();
|
worker.start();
|
||||||
@@ -149,7 +209,9 @@ 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 for more data to send
|
// the acked/sent messages in the DB, and check the connection
|
||||||
|
// 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));
|
||||||
@@ -157,6 +219,7 @@ 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());
|
||||||
@@ -172,11 +235,41 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCancelsApiCallWhenDestroyed() throws Exception {
|
public void testDoesNotWriteFileIfContactConnectsDuringConnectivityCheck()
|
||||||
// When the worker is started it should check for data to send. As
|
throws Exception {
|
||||||
// there's data ready to send immediately, the worker should start a
|
// When the worker is started it should check the connection registry.
|
||||||
// connectivity check
|
// 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();
|
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
|
||||||
|
public void testCancelsApiCallWhenDestroyed() 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();
|
expectCheckForDataToSendAndStartConnectivityCheck();
|
||||||
|
|
||||||
worker.start();
|
worker.start();
|
||||||
@@ -212,9 +305,7 @@ 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
|
||||||
context.checking(new Expectations() {{
|
expectCancelTask(apiCall);
|
||||||
oneOf(apiCall).cancel();
|
|
||||||
}});
|
|
||||||
expectRemoveObserverAndListener();
|
expectRemoveObserverAndListener();
|
||||||
|
|
||||||
worker.destroy();
|
worker.destroy();
|
||||||
@@ -230,16 +321,21 @@ 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 for data to send. As
|
// When the worker is started it should check the connection registry.
|
||||||
// the data isn't ready to send immediately, the worker should
|
// We're not connected to the contact, so the worker should check for
|
||||||
// schedule a wakeup
|
// data to send. As the data isn't ready to send immediately, the
|
||||||
|
// 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 for data to send
|
// When the wakeup task runs it should check the connection registry.
|
||||||
|
// 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();
|
||||||
@@ -252,21 +348,51 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCancelsWakeupIfDestroyedBeforeWakingUp() throws Exception {
|
public void testCancelsWakeupIfContactConnectsBeforeWakingUp()
|
||||||
|
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
|
||||||
context.checking(new Expectations() {{
|
expectCancelTask(wakeupTask);
|
||||||
oneOf(wakeupTask).cancel();
|
|
||||||
}});
|
|
||||||
expectRemoveObserverAndListener();
|
expectRemoveObserverAndListener();
|
||||||
|
|
||||||
worker.destroy();
|
worker.destroy();
|
||||||
@@ -279,10 +405,12 @@ 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 for data to send. As
|
// When the worker is started it should check the connection registry.
|
||||||
// the data isn't ready to send immediately, the worker should
|
// We're not connected to the contact, so the worker should check for
|
||||||
// schedule a wakeup
|
// data to send. As the data isn't ready to send immediately, the
|
||||||
|
// worker should schedule a wakeup
|
||||||
expectRunTaskOnIoExecutor();
|
expectRunTaskOnIoExecutor();
|
||||||
|
expectCheckConnectionRegistry(false);
|
||||||
AtomicReference<Runnable> wakeup = new AtomicReference<>();
|
AtomicReference<Runnable> wakeup = new AtomicReference<>();
|
||||||
expectCheckForDataToSendAndScheduleWakeup(wakeup);
|
expectCheckForDataToSendAndScheduleWakeup(wakeup);
|
||||||
|
|
||||||
@@ -293,11 +421,10 @@ 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);
|
||||||
context.checking(new Expectations() {{
|
expectCancelTask(wakeupTask);
|
||||||
oneOf(wakeupTask).cancel();
|
|
||||||
}});
|
|
||||||
|
|
||||||
worker.eventOccurred(new MessageSharedEvent(newMessageId));
|
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId,
|
||||||
|
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
|
||||||
@@ -306,9 +433,13 @@ 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));
|
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId,
|
||||||
|
groupVisibility));
|
||||||
|
|
||||||
// When the check task runs, it should check for new data
|
// When the check task runs, it should check the connection registry.
|
||||||
|
// 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();
|
||||||
@@ -322,8 +453,11 @@ 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 for data to send
|
// 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();
|
expectRunTaskOnIoExecutor();
|
||||||
|
expectCheckConnectionRegistry(false);
|
||||||
expectCheckForDataToSendNoDataWaiting();
|
expectCheckForDataToSendNoDataWaiting();
|
||||||
|
|
||||||
worker.start();
|
worker.start();
|
||||||
@@ -334,13 +468,12 @@ 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));
|
worker.eventOccurred(new MessageSharedEvent(newMessageId, groupId,
|
||||||
|
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
|
||||||
context.checking(new Expectations() {{
|
expectCancelTask(checkTask);
|
||||||
oneOf(checkTask).cancel();
|
|
||||||
}});
|
|
||||||
expectRemoveObserverAndListener();
|
expectRemoveObserverAndListener();
|
||||||
|
|
||||||
worker.destroy();
|
worker.destroy();
|
||||||
@@ -350,13 +483,100 @@ 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 for data to send. As
|
// When the worker is started it should check the connection registry.
|
||||||
// there's data ready to send immediately, the worker should start a
|
// We're not connected to the contact, so the worker should check for
|
||||||
// connectivity check
|
// data to send. As there's data ready to send immediately, the worker
|
||||||
|
// should start a connectivity check
|
||||||
expectRunTaskOnIoExecutor();
|
expectRunTaskOnIoExecutor();
|
||||||
|
expectCheckConnectionRegistry(false);
|
||||||
expectCheckForDataToSendAndStartConnectivityCheck();
|
expectCheckForDataToSendAndStartConnectivityCheck();
|
||||||
|
|
||||||
worker.start();
|
worker.start();
|
||||||
@@ -375,7 +595,10 @@ public class MailboxUploadWorkerTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
worker.onConnectivityCheckSucceeded();
|
worker.onConnectivityCheckSucceeded();
|
||||||
|
|
||||||
// When the check task runs it should check for new data
|
// When the check task runs it should check the connection registry.
|
||||||
|
// 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();
|
||||||
@@ -387,6 +610,13 @@ 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)));
|
||||||
@@ -456,6 +686,12 @@ 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);
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
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();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,9 +12,11 @@ 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;
|
||||||
|
|
||||||
@@ -27,12 +29,14 @@ 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 {
|
||||||
|
|||||||
@@ -12,16 +12,19 @@ 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 CircumventionProviderTest extends BrambleTestCase {
|
public class CircumventionProviderImplTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CircumventionProvider provider =
|
private final CircumventionProviderImpl provider =
|
||||||
new CircumventionProviderImpl();
|
new CircumventionProviderImpl();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -56,13 +59,31 @@ public class CircumventionProviderTest extends BrambleTestCase {
|
|||||||
provider.getSuitableBridgeTypes(country));
|
provider.getSuitableBridgeTypes(country));
|
||||||
}
|
}
|
||||||
for (String country : DPI_BRIDGES) {
|
for (String country : DPI_BRIDGES) {
|
||||||
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK),
|
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE),
|
||||||
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);
|
||||||
@@ -14,11 +14,13 @@ 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;
|
||||||
@@ -68,13 +70,10 @@ 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, MAX_MESSAGE_IDS);
|
oneOf(db).getMessagesToAck(noAckIdTxn, contactId);
|
||||||
will(returnValue(emptyList()));
|
will(returnValue(emptyList()));
|
||||||
// Calculate capacity for messages
|
// Calculate capacity for messages
|
||||||
oneOf(recordWriter).getBytesWritten();
|
oneOf(recordWriter).getBytesWritten();
|
||||||
@@ -106,7 +105,6 @@ 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);
|
||||||
|
|
||||||
@@ -114,28 +112,24 @@ 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)));
|
||||||
// No more messages to ack
|
will(new CaptureArgumentAction<>(ack, Ack.class, 0));
|
||||||
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));
|
||||||
@@ -162,6 +156,7 @@ 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());
|
||||||
}
|
}
|
||||||
@@ -178,48 +173,50 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
eventBus, contactId, transportId, MAX_LATENCY,
|
eventBus, contactId, transportId, MAX_LATENCY,
|
||||||
streamWriter, recordWriter, sessionRecord, capacity);
|
streamWriter, recordWriter, sessionRecord, capacity);
|
||||||
|
|
||||||
Transaction ackIdTxn1 = new Transaction(null, true);
|
Transaction ackIdTxn = 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;
|
||||||
|
|
||||||
List<MessageId> idsInFirstAck = new ArrayList<>(MAX_MESSAGE_IDS);
|
// There are MAX_MESSAGE_IDS + 2 messages that need to be acked, but
|
||||||
for (int i = 0; i < MAX_MESSAGE_IDS; i++) {
|
// only enough capacity to ack MAX_MESSAGE_IDS + 1 messages
|
||||||
idsInFirstAck.add(new MessageId(getRandomId()));
|
List<MessageId> idsToAck = new ArrayList<>(MAX_MESSAGE_IDS + 2);
|
||||||
|
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 =
|
||||||
singletonList(new MessageId(getRandomId()));
|
idsToAck.subList(MAX_MESSAGE_IDS, MAX_MESSAGE_IDS + 1);
|
||||||
List<MessageId> allIds = new ArrayList<>(MAX_MESSAGE_IDS + 1);
|
List<MessageId> idsAcked = idsToAck.subList(0, MAX_MESSAGE_IDS + 1);
|
||||||
allIds.addAll(idsInFirstAck);
|
|
||||||
allIds.addAll(idsInSecondAck);
|
AtomicReference<Ack> firstAck = new AtomicReference<>();
|
||||||
|
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
|
||||||
@@ -236,7 +233,9 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
session.run();
|
session.run();
|
||||||
|
|
||||||
assertEquals(allIds, sessionRecord.getAckedIds());
|
assertEquals(idsAcked, sessionRecord.getAckedIds());
|
||||||
|
assertEquals(idsInFirstAck, firstAck.get().getMessageIds());
|
||||||
|
assertEquals(idsInSecondAck, secondAck.get().getMessageIds());
|
||||||
assertEquals(emptyList(), sessionRecord.getSentIds());
|
assertEquals(emptyList(), sessionRecord.getSentIds());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ 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;
|
||||||
|
|
||||||
@@ -11,7 +13,9 @@ 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 {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ 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 {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.test;
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
@@ -9,6 +9,8 @@ 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;
|
||||||
|
|
||||||
@@ -17,7 +19,9 @@ 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 {
|
||||||
|
|||||||
@@ -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.4:jtorctl-0.4.jar:4e61f59dc9f3984438a7151c4df8d7c1f83d5fb3eb8c151acfc794a8fef85a36',
|
'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
|
||||||
'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',
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ 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"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
@@ -9,6 +10,7 @@ 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
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ 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;
|
||||||
@@ -42,11 +44,10 @@ 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;
|
||||||
@@ -58,6 +59,8 @@ 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 =
|
||||||
@@ -69,29 +72,39 @@ 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 : provider.getBridges(DEFAULT_OBFS4)) {
|
for (String bridge :
|
||||||
|
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 : provider.getBridges(NON_DEFAULT_OBFS4)) {
|
for (String bridge :
|
||||||
states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats, false));
|
provider.getBridges(NON_DEFAULT_OBFS4, "", true)) {
|
||||||
|
states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats,
|
||||||
|
false));
|
||||||
}
|
}
|
||||||
for (String bridge : provider.getBridges(VANILLA)) {
|
for (String bridge : provider.getBridges(VANILLA, "", true)) {
|
||||||
states.add(new Params(bridge, VANILLA, stats, false));
|
states.add(new Params(bridge, VANILLA, stats, false));
|
||||||
}
|
}
|
||||||
for (String bridge : provider.getBridges(MEEK)) {
|
for (String bridge : provider.getBridges(MEEK, "", true)) {
|
||||||
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 OBFS4_TIMEOUT = MINUTES.toMillis(2);
|
private final static long 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());
|
||||||
|
|
||||||
@@ -115,6 +128,12 @@ 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;
|
||||||
@@ -160,15 +179,16 @@ 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,
|
batteryManager, clock, crypto, torDir, torSocksPort,
|
||||||
SOCKS_PORT, CONTROL_PORT);
|
torControlPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -187,8 +207,7 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
try {
|
try {
|
||||||
plugin.start();
|
plugin.start();
|
||||||
long start = clock.currentTimeMillis();
|
long start = clock.currentTimeMillis();
|
||||||
long timeout = params.bridgeType == MEEK
|
long timeout = params.bridgeType == MEEK ? MEEK_TIMEOUT : TIMEOUT;
|
||||||
? 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);
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ 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 {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,10 +24,12 @@ 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.12:obfs4proxy-linux-0.0.12.jar:3dd83aff25fe1cb3e4eab78a02c76ac921f552be6877b3af83a472438525df2a',
|
'org.briarproject:obfs4proxy-linux:0.0.14:obfs4proxy-linux-0.0.14.jar:6391d323d45a279362236c7c62e21b903d07d4f31f5e0c8d49d009769b720cc6',
|
||||||
'org.briarproject:obfs4proxy-windows:0.0.12:obfs4proxy-windows-0.0.12.jar:392aa4b9d9c6fef0c659c4068d019d6c6471991bbb62ff00553884ec36018c7b',
|
'org.briarproject:obfs4proxy-windows:0.0.14:obfs4proxy-windows-0.0.14.jar:801d48525f52583a470a1671026b87992176d4432b299774989387cb87bc8ba3',
|
||||||
'org.briarproject:tor-linux:0.4.5.12-2:tor-linux-0.4.5.12-2.jar:d275f323faf5e70b33d2c8a1bdab1bb3ab5a0d8f4e23c4a6dda03d86f4e95838',
|
'org.briarproject:snowflake-linux:2.3.1:snowflake-linux-2.3.1.jar:99ecf4546d8f79eb8408168c09380fec596558ac934554bf7d4247ea7ef2c9f3',
|
||||||
'org.briarproject:tor-windows:0.4.5.12-2:tor-windows-0.4.5.12-2.jar:46599a15d099ed35a360113293f66acc119571c24ec2e37e85e4fb54b4722e07',
|
'org.briarproject:snowflake-windows:2.3.1:snowflake-windows-2.3.1.jar:d011f1a72c00a221f56380c19aad8ff11db8c2bb1adb0784125572d80b4d275a',
|
||||||
|
'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',
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10410
|
versionCode 10412
|
||||||
versionName "1.4.10"
|
versionName "1.4.12"
|
||||||
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",
|
||||||
|
|||||||
@@ -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.ic_mailbox);
|
b.setSmallIcon(R.drawable.notification_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));
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ 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;
|
||||||
|
|
||||||
@@ -26,6 +28,10 @@ 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);
|
||||||
@@ -35,6 +41,11 @@ 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) {
|
||||||
@@ -50,6 +61,29 @@ 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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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;
|
||||||
@@ -23,6 +24,7 @@ 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
|
||||||
@@ -215,6 +217,8 @@ 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
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;
|
||||||
@@ -10,6 +13,7 @@ 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;
|
||||||
@@ -18,6 +22,8 @@ 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;
|
||||||
@@ -27,14 +33,20 @@ 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;
|
||||||
@@ -43,6 +55,7 @@ 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;
|
||||||
@@ -73,6 +86,7 @@ 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);
|
||||||
@@ -90,6 +104,8 @@ 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);
|
||||||
@@ -161,4 +177,16 @@ 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,9 +28,6 @@ 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;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<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.
|
After Width: | Height: | Size: 407 B |
Binary file not shown.
|
After Width: | Height: | Size: 274 B |
Binary file not shown.
|
After Width: | Height: | Size: 651 B |
Binary file not shown.
|
After Width: | Height: | Size: 858 B |
12
briar-android/src/main/res/drawable/ic_info_dark.xml
Normal file
12
briar-android/src/main/res/drawable/ic_info_dark.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<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>
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?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>
|
|
||||||
82
briar-android/src/main/res/layout/fragment_about.xml
Normal file
82
briar-android/src/main/res/layout/fragment_about.xml
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?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>
|
||||||
@@ -1,119 +1,126 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView 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">
|
||||||
|
|
||||||
<ImageView
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/imageView"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="32dp"
|
android:layout_height="wrap_content">
|
||||||
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" />
|
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/statusTitleView"
|
android:id="@+id/imageView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="32dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="32dp"
|
||||||
android:layout_margin="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:gravity="center"
|
app:layout_constraintBottom_toTopOf="@+id/statusTitleView"
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/statusMessageView"
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
app:layout_constraintVertical_bias="0.25"
|
||||||
tools:text="@string/mailbox_status_problem_title" />
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:srcCompat="@drawable/ic_help_outline_white"
|
||||||
|
tools:tint="@color/briar_orange_500" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/statusMessageView"
|
android:id="@+id/statusTitleView"
|
||||||
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_margin="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:gravity="center"
|
||||||
android:gravity="center"
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
app:layout_constrainedWidth="true"
|
||||||
android:visibility="gone"
|
app:layout_constraintBottom_toTopOf="@+id/statusMessageView"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/checkButton"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
tools:text="@string/mailbox_status_problem_title" />
|
||||||
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/statusMessageView"
|
||||||
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"
|
||||||
app:buttonStyle="@style/BriarButtonFlat.Neutral"
|
android:layout_marginBottom="16dp"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
|
android:gravity="center"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:visibility="gone"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/statusMessageView"
|
app:layout_constrainedWidth="true"
|
||||||
app:text="@string/mailbox_status_check_button" />
|
app:layout_constraintBottom_toTopOf="@+id/checkButton"
|
||||||
|
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" />
|
||||||
|
|
||||||
<TextView
|
<org.briarproject.briar.android.view.BriarButton
|
||||||
android:id="@+id/statusInfoView"
|
android:id="@+id/checkButton"
|
||||||
android:layout_width="0dp"
|
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"
|
app:buttonStyle="@style/BriarButtonFlat.Neutral"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
|
app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
|
||||||
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/checkButton"
|
app:layout_constraintTop_toBottomOf="@+id/statusMessageView"
|
||||||
tools:text="@string/mailbox_status_connected_info" />
|
app:text="@string/mailbox_status_check_button" />
|
||||||
|
|
||||||
<Button
|
<TextView
|
||||||
android:id="@+id/wizardButton"
|
android:id="@+id/statusInfoView"
|
||||||
style="@style/BriarButtonFlat.Positive"
|
android:layout_width="0dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_margin="16dp"
|
||||||
android:layout_margin="16dp"
|
android:gravity="center"
|
||||||
android:text="@string/mailbox_error_wizard_button"
|
app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
|
||||||
android:visibility="gone"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:drawableTint="@color/briar_button_text_positive"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
|
app:layout_constraintTop_toBottomOf="@+id/checkButton"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
tools:text="@string/mailbox_status_connected_info" />
|
||||||
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/unlinkButton"
|
android:id="@+id/wizardButton"
|
||||||
style="@style/BriarButtonFlat.Negative"
|
style="@style/BriarButtonFlat.Positive"
|
||||||
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_status_unlink_button"
|
android:text="@string/mailbox_error_wizard_button"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:drawableTint="@color/briar_button_text_positive"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
|
||||||
|
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" />
|
||||||
|
|
||||||
<ProgressBar
|
<Button
|
||||||
android:id="@+id/unlinkProgress"
|
android:id="@+id/unlinkButton"
|
||||||
android:layout_width="wrap_content"
|
style="@style/BriarButtonFlat.Negative"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:visibility="invisible"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/unlinkButton"
|
android:layout_margin="16dp"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/unlinkButton"
|
android:text="@string/mailbox_status_unlink_button"
|
||||||
app:layout_constraintStart_toStartOf="@+id/unlinkButton"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/unlinkButton"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
tools:visibility="visible" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<ProgressBar
|
||||||
|
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>
|
||||||
|
|||||||
@@ -56,6 +56,18 @@
|
|||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/PrivacyPolicy"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/margin_large"
|
||||||
|
android:layout_marginRight="@dimen/margin_large"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
android:text="@string/privacy_policy"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/user_email_layout" />
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/include_debug_report"
|
android:id="@+id/include_debug_report"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -74,9 +86,10 @@
|
|||||||
style="@style/BriarButtonFlat.Positive"
|
style="@style/BriarButtonFlat.Positive"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
android:text="@string/show"
|
android:text="@string/show"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/user_email_layout" />
|
app:layout_constraintTop_toBottomOf="@+id/PrivacyPolicy" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list"
|
android:id="@+id/list"
|
||||||
|
|||||||
@@ -55,13 +55,42 @@
|
|||||||
android:paddingEnd="@dimen/margin_medium"
|
android:paddingEnd="@dimen/margin_medium"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/dateView"
|
app:layout_constraintBottom_toTopOf="@+id/trustIndicatorDescription"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/bulbView"
|
app:layout_constraintEnd_toStartOf="@+id/bulbView"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toEndOf="@+id/avatarFrameView"
|
app:layout_constraintStart_toEndOf="@+id/avatarFrameView"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_chainStyle="packed"
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
tools:text="This is a name of a contact" />
|
tools:text="This is a name of a contact" />
|
||||||
|
|
||||||
|
<org.briarproject.briar.android.view.TrustIndicatorView
|
||||||
|
android:id="@+id/trustIndicator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/dateView"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/trustIndicatorDescription"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/avatarFrameView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/nameView"
|
||||||
|
tools:src="@drawable/trust_indicator_verified" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/trustIndicatorDescription"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:paddingStart="@dimen/margin_medium"
|
||||||
|
android:paddingEnd="@dimen/margin_medium"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="@dimen/text_size_small"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/dateView"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/bulbView"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/trustIndicator"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/nameView"
|
||||||
|
tools:text="verified contact"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/dateView"
|
android:id="@+id/dateView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -70,15 +99,16 @@
|
|||||||
android:layout_marginLeft="@dimen/margin_medium"
|
android:layout_marginLeft="@dimen/margin_medium"
|
||||||
android:layout_marginEnd="@dimen/margin_medium"
|
android:layout_marginEnd="@dimen/margin_medium"
|
||||||
android:layout_marginRight="@dimen/margin_medium"
|
android:layout_marginRight="@dimen/margin_medium"
|
||||||
android:layout_marginBottom="@dimen/listitem_vertical_margin"
|
android:layout_marginBottom="10dp"
|
||||||
android:paddingStart="@dimen/margin_medium"
|
android:paddingStart="@dimen/margin_medium"
|
||||||
android:paddingEnd="@dimen/margin_medium"
|
android:paddingEnd="@dimen/margin_medium"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:textSize="@dimen/text_size_small"
|
android:textSize="@dimen/text_size_small"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/divider"
|
app:layout_constraintBottom_toTopOf="@id/divider"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/bulbView"
|
app:layout_constraintEnd_toStartOf="@+id/bulbView"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
app:layout_constraintStart_toEndOf="@+id/avatarFrameView"
|
app:layout_constraintStart_toEndOf="@+id/avatarFrameView"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/nameView"
|
app:layout_constraintTop_toBottomOf="@+id/trustIndicatorDescription"
|
||||||
tools:text="Dec 24" />
|
tools:text="Dec 24" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user