mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
222 Commits
1387-persi
...
beta-1.4.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5e17c8201 | ||
|
|
d572ae71e7 | ||
|
|
2e9d9dac84 | ||
|
|
573817c4c9 | ||
|
|
4f00f39d3f | ||
|
|
c7d3628ecb | ||
|
|
b198bef5f8 | ||
|
|
cff94009a1 | ||
|
|
44f9f0bbc5 | ||
|
|
5fdb43ce9b | ||
|
|
725d11d960 | ||
|
|
7cf2c2faa7 | ||
|
|
4b3c26feb6 | ||
|
|
2fbeb29195 | ||
|
|
5892fba237 | ||
|
|
cc9f04980a | ||
|
|
44fb2a5c59 | ||
|
|
68e534348f | ||
|
|
795a8f1e70 | ||
|
|
bf968b227e | ||
|
|
8b94dad01f | ||
|
|
fa0610fff1 | ||
|
|
1d94db8d60 | ||
|
|
1d4f450960 | ||
|
|
7f6b31d36c | ||
|
|
05737d858d | ||
|
|
2c8e2ab6b8 | ||
|
|
97f64fb31c | ||
|
|
e66152e812 | ||
|
|
101ffa2f08 | ||
|
|
13eebe393a | ||
|
|
5bc5791ddb | ||
|
|
a35e9af1de | ||
|
|
ade89c14c4 | ||
|
|
16cfb89310 | ||
|
|
78f00863dd | ||
|
|
bd50a109cd | ||
|
|
38c91aea32 | ||
|
|
92517ae7c0 | ||
|
|
dd1c8c8301 | ||
|
|
edc1029e92 | ||
|
|
27e9338a12 | ||
|
|
243df3096a | ||
|
|
50f9718037 | ||
|
|
88c8bd32a5 | ||
|
|
3e597ceff8 | ||
|
|
3d6972fd73 | ||
|
|
288f3331ec | ||
|
|
a14ee55f12 | ||
|
|
2a85907565 | ||
|
|
1fe7b2f451 | ||
|
|
585ceb626b | ||
|
|
5da782cf18 | ||
|
|
ec6b999d30 | ||
|
|
d8a925a94f | ||
|
|
3de4386e63 | ||
|
|
8c60787866 | ||
|
|
fa8ca8e6cf | ||
|
|
07814d43de | ||
|
|
d80ba0f556 | ||
|
|
d70e1ed32e | ||
|
|
eec2c87797 | ||
|
|
a256027916 | ||
|
|
bf0f99277a | ||
|
|
2d62deb2db | ||
|
|
e3682bb331 | ||
|
|
6805040ac4 | ||
|
|
4198e1f22a | ||
|
|
ee11d2a28d | ||
|
|
f3718e496c | ||
|
|
414c296abd | ||
|
|
79051439c5 | ||
|
|
32b62d3e30 | ||
|
|
e3f2a30120 | ||
|
|
58a122ee28 | ||
|
|
f5f7b3eb51 | ||
|
|
098128c8a8 | ||
|
|
27d566df7a | ||
|
|
9469825f4f | ||
|
|
5ce90422c6 | ||
|
|
256662e094 | ||
|
|
dc7f1e0c86 | ||
|
|
a54e1d424c | ||
|
|
9fa3ee18a4 | ||
|
|
4df523aaf8 | ||
|
|
84be347695 | ||
|
|
6783eae1b1 | ||
|
|
fe58bd8f86 | ||
|
|
952ac2c922 | ||
|
|
4390c810d1 | ||
|
|
1a1b26d8f2 | ||
|
|
a567301e49 | ||
|
|
5e8d5c96fc | ||
|
|
80d804d280 | ||
|
|
7fad299cf0 | ||
|
|
4e90641059 | ||
|
|
f7892050ea | ||
|
|
003ecdb81f | ||
|
|
9141a8bb3b | ||
|
|
7ba2af077e | ||
|
|
ce7f44de01 | ||
|
|
4a46b13e9d | ||
|
|
ae7ccdf34c | ||
|
|
88c54ed3b0 | ||
|
|
653b744a02 | ||
|
|
65e7bcb94e | ||
|
|
d6bbe59d3a | ||
|
|
98dddf3572 | ||
|
|
6d22bab5ee | ||
|
|
7ae91a984f | ||
|
|
fb50a5ba45 | ||
|
|
80bc409225 | ||
|
|
80cac277ac | ||
|
|
888aea4b37 | ||
|
|
e9d3f600fa | ||
|
|
3055338ea8 | ||
|
|
e4a7b1731a | ||
|
|
2da8c19d3e | ||
|
|
237ac50b01 | ||
|
|
73d9e05ada | ||
|
|
e14773985d | ||
|
|
8b3dae6daf | ||
|
|
065ceb8e98 | ||
|
|
6d881892c7 | ||
|
|
16b503dd7b | ||
|
|
fc5533ec6e | ||
|
|
5c153aeb6c | ||
|
|
36670a8bf6 | ||
|
|
32d62f9960 | ||
|
|
eafd6a1ca1 | ||
|
|
1614e72c43 | ||
|
|
d3beb850ef | ||
|
|
f057f0859b | ||
|
|
61ea7ff8de | ||
|
|
0fba65a722 | ||
|
|
3a191908c0 | ||
|
|
482258fc92 | ||
|
|
0cb2dcf6b7 | ||
|
|
76599a8d04 | ||
|
|
173af62dec | ||
|
|
a53a49e543 | ||
|
|
78b993bda4 | ||
|
|
6b956611e7 | ||
|
|
d0c3c1f9f6 | ||
|
|
24d058cdcc | ||
|
|
a9ab7fd60f | ||
|
|
9e5201d571 | ||
|
|
39eebe4c02 | ||
|
|
171df265ab | ||
|
|
9436757215 | ||
|
|
75370c8124 | ||
|
|
10dceafde1 | ||
|
|
e3126f931e | ||
|
|
6ddedbba36 | ||
|
|
982637a0b0 | ||
|
|
78ef8c8117 | ||
|
|
7319398c3b | ||
|
|
841b8133d1 | ||
|
|
b334e8da27 | ||
|
|
0ac26883c6 | ||
|
|
519837e829 | ||
|
|
9fa54bf15c | ||
|
|
af3389e0e1 | ||
|
|
f5cdad9100 | ||
|
|
df4e6aa207 | ||
|
|
82443d9708 | ||
|
|
27058ba0ca | ||
|
|
f400cf5aa0 | ||
|
|
e52c5ddc8e | ||
|
|
835e9f6994 | ||
|
|
4193179eb8 | ||
|
|
421b00517f | ||
|
|
707802c459 | ||
|
|
9f1757ccaf | ||
|
|
d665fc17ec | ||
|
|
65be2d2b26 | ||
|
|
d2a39da3e0 | ||
|
|
d13e4c976e | ||
|
|
20b52804bf | ||
|
|
5b27eb354c | ||
|
|
c340071469 | ||
|
|
506e274dff | ||
|
|
423356fdda | ||
|
|
043a173828 | ||
|
|
f0501bbfab | ||
|
|
5cafde7b14 | ||
|
|
5117dbad7e | ||
|
|
3a22388495 | ||
|
|
1d4de46dfd | ||
|
|
d805069dfe | ||
|
|
74cb2a6ce5 | ||
|
|
2880a4adac | ||
|
|
e032e0ccd5 | ||
|
|
38a07e1d54 | ||
|
|
07b35db4d4 | ||
|
|
3b03db9f43 | ||
|
|
de3a74eedf | ||
|
|
5a39f9730f | ||
|
|
bdf02bbc6c | ||
|
|
d5b2ebdb23 | ||
|
|
98bb8d4af1 | ||
|
|
75cc19e578 | ||
|
|
aad87e6e98 | ||
|
|
dad895c30d | ||
|
|
f8b3d79813 | ||
|
|
0a98566298 | ||
|
|
93a03d7e15 | ||
|
|
3eb3dbde09 | ||
|
|
fd56176450 | ||
|
|
d29812f055 | ||
|
|
403601b9f2 | ||
|
|
992215b78a | ||
|
|
658ca8de21 | ||
|
|
e0e2c0cc89 | ||
|
|
114d80ad43 | ||
|
|
8d5803098b | ||
|
|
a9ed9da822 | ||
|
|
2295db4361 | ||
|
|
7aa1073bf5 | ||
|
|
6c702bad0a | ||
|
|
d3dbcfd62d | ||
|
|
c4c70f5ac2 |
@@ -32,8 +32,9 @@ test:
|
||||
extends: .base-test
|
||||
stage: test
|
||||
script:
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom assembleOfficialDebug :briar-headless:linuxJars
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: always
|
||||
@@ -61,7 +62,7 @@ android test:
|
||||
when: on_failure
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
when: on_success
|
||||
when: manual
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
changes:
|
||||
- briar-android/**/*
|
||||
@@ -84,35 +85,43 @@ test_reproducible:
|
||||
|
||||
.optional_tests:
|
||||
stage: optional_tests
|
||||
before_script:
|
||||
- set -e
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
|
||||
cache:
|
||||
key: "$CI_COMMIT_REF_SLUG"
|
||||
paths:
|
||||
- .gradle/wrapper
|
||||
- .gradle/caches
|
||||
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
|
||||
after_script:
|
||||
# these file change every time but should not be cached
|
||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||
extends: .base-test
|
||||
|
||||
bridge test:
|
||||
extends: .optional_tests
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
when: on_success
|
||||
allow_failure: true
|
||||
allow_failure: false
|
||||
- if: '$CI_COMMIT_TAG == null'
|
||||
when: manual
|
||||
allow_failure: true
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
timeout: 3h
|
||||
|
||||
mailbox integration test:
|
||||
extends: .optional_tests
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
when: on_success
|
||||
- if: '$CI_COMMIT_TAG == null'
|
||||
when: manual
|
||||
allow_failure: true # TODO figure out how not to allow failure while leaving this optional
|
||||
script:
|
||||
# start mailbox
|
||||
- cd /opt && git clone --depth 1 https://code.briarproject.org/briar/briar-mailbox.git briar-mailbox
|
||||
- cd briar-mailbox
|
||||
- mkdir -p /root/.local/share # create directory that mailbox (currently) expects to exist
|
||||
- ./gradlew run --args="--debug --setup-token 54686973206973206120736574757020746f6b656e20666f722042726961722e" &
|
||||
# run mailbox integration test once mailbox has started
|
||||
- cd "$CI_PROJECT_DIR"
|
||||
- bramble-core/src/test/bash/wait-for-mailbox.sh
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
|
||||
|
||||
pre_release_tests:
|
||||
extends: .optional_tests
|
||||
script:
|
||||
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||
only:
|
||||
- tags
|
||||
|
||||
@@ -22,6 +22,15 @@ our site.
|
||||
|
||||
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
|
||||
|
||||
## Reproducible builds
|
||||
|
||||
We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer)
|
||||
to ease the task of verifying that the published APK binaries
|
||||
include nothing but our publicly available source code.
|
||||
|
||||
You can either use those images or use them as a blueprint to build your own environment
|
||||
for reproduction.
|
||||
|
||||
## Donate
|
||||
|
||||
[](https://liberapay.com/Briar/donate) [](https://flattr.com/t/592836/)
|
||||
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10401
|
||||
versionName "1.4.1"
|
||||
versionCode 10406
|
||||
versionName "1.4.6"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -43,7 +43,7 @@ configurations {
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
tor "org.briarproject:tor-android:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version@zip"
|
||||
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
@@ -53,7 +53,7 @@ dependencies {
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-imposters:$jmock_version"
|
||||
}
|
||||
|
||||
def torBinariesDir = 'src/main/res/raw'
|
||||
@@ -70,11 +70,6 @@ clean.dependsOn cleanTorBinaries
|
||||
|
||||
task unpackTorBinaries {
|
||||
doLast {
|
||||
copy {
|
||||
from configurations.tor.collect { zipTree(it) }
|
||||
into torBinariesDir
|
||||
include 'geoip.zip'
|
||||
}
|
||||
configurations.tor.each { outer ->
|
||||
zipTree(outer).each { inner ->
|
||||
if (inner.name.endsWith('_arm_pie.zip')) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
-keep,includedescriptorclasses class org.briarproject.** { *; }
|
||||
|
||||
-keep class org.h2.** { *; }
|
||||
# Keep the H2 classes that are loaded via reflection
|
||||
-keep class org.h2.Driver { *; }
|
||||
-keep class org.h2.engine.Engine { *; }
|
||||
-keep class org.h2.store.fs.** { *; }
|
||||
# Don't warn about unused dependencies of H2 classes
|
||||
-dontwarn org.h2.**
|
||||
-dontnote org.h2.**
|
||||
|
||||
@@ -15,5 +17,4 @@
|
||||
-dontwarn sun.misc.Unsafe
|
||||
-dontnote com.google.common.**
|
||||
|
||||
# UPnP library isn't used
|
||||
-dontwarn org.bitlet.weupnp.**
|
||||
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
|
||||
|
||||
@@ -39,6 +39,6 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
return app.getContentResolver().openOutputStream(Uri.parse(uri));
|
||||
return app.getContentResolver().openOutputStream(Uri.parse(uri), "wt");
|
||||
}
|
||||
}
|
||||
|
||||
0
bramble-android/src/main/res/raw/.gitkeep
Normal file
0
bramble-android/src/main/res/raw/.gitkeep
Normal file
@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -44,7 +44,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
||||
private AndroidAccountManager accountManager;
|
||||
|
||||
public AndroidAccountManagerTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||
app = context.mock(Application.class);
|
||||
applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.dataDir = testDir.getAbsolutePath();
|
||||
|
||||
@@ -87,8 +87,8 @@ dependencyVerification {
|
||||
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
|
||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
|
||||
'org.briarproject:tor-android:0.3.5.17:tor-android-0.3.5.17.jar:1888afc10a26b93d00a010ea27bf0b1b162a6d524688b08b98d70d14dc363b54',
|
||||
'org.briarproject:obfs4proxy-android:0.0.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89',
|
||||
'org.briarproject:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
|
||||
|
||||
@@ -9,11 +9,11 @@ apply from: 'witness.gradle'
|
||||
dependencies {
|
||||
implementation "com.google.dagger:dagger:$dagger_version"
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
|
||||
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmock_version"
|
||||
|
||||
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
||||
}
|
||||
|
||||
@@ -10,4 +10,12 @@ public interface FeatureFlags {
|
||||
boolean shouldEnableProfilePictures();
|
||||
|
||||
boolean shouldEnableDisappearingMessages();
|
||||
|
||||
boolean shouldEnableMailbox();
|
||||
|
||||
boolean shouldEnablePrivateGroupsInCore();
|
||||
|
||||
boolean shouldEnableForumsInCore();
|
||||
|
||||
boolean shouldEnableBlogsInCore();
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public abstract class UniqueId extends Bytes {
|
||||
public class UniqueId extends Bytes {
|
||||
|
||||
/**
|
||||
* The length of a unique identifier in bytes.
|
||||
*/
|
||||
public static final int LENGTH = 32;
|
||||
|
||||
protected UniqueId(byte[] id) {
|
||||
public UniqueId(byte[] id) {
|
||||
super(id);
|
||||
if (id.length != LENGTH) throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.feed;
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@@ -13,7 +13,7 @@ import javax.inject.Provider;
|
||||
* collected.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
abstract class WeakSingletonProvider<T> implements Provider<T> {
|
||||
public abstract class WeakSingletonProvider<T> implements Provider<T> {
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
@@ -31,5 +31,5 @@ abstract class WeakSingletonProvider<T> implements Provider<T> {
|
||||
}
|
||||
}
|
||||
|
||||
abstract T createInstance();
|
||||
public abstract T createInstance();
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -20,6 +21,8 @@ import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface ClientHelper {
|
||||
|
||||
@@ -123,6 +126,18 @@ public interface ClientHelper {
|
||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||
BdfDictionary properties) throws FormatException;
|
||||
|
||||
/**
|
||||
* Parse and validate the property dictionary of a Mailbox property update
|
||||
* message.
|
||||
*
|
||||
* @return the properties for using the Mailbox, or null if there is no
|
||||
* Mailbox available
|
||||
* @throws FormatException if the properties are not valid
|
||||
*/
|
||||
@Nullable
|
||||
MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
||||
BdfDictionary properties) throws FormatException;
|
||||
|
||||
/**
|
||||
* Retrieves the contact ID from the group metadata of the given contact
|
||||
* group.
|
||||
|
||||
@@ -107,6 +107,32 @@ public interface ContactManager {
|
||||
*/
|
||||
String getHandshakeLink() throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the handshake link that needs to be sent to a contact we want
|
||||
* to add.
|
||||
*/
|
||||
String getHandshakeLink(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Creates a {@link PendingContact} from the given handshake link and
|
||||
* alias, adds it to the database and returns it.
|
||||
*
|
||||
* @param link The handshake link received from the pending contact
|
||||
* @param alias The alias the user has given this pending contact
|
||||
* @throws UnsupportedVersionException If the link uses a format version
|
||||
* that is not supported
|
||||
* @throws FormatException If the link is invalid
|
||||
* @throws GeneralSecurityException If the pending contact's handshake
|
||||
* public key is invalid
|
||||
* @throws ContactExistsException If a contact with the same handshake
|
||||
* public key already exists
|
||||
* @throws PendingContactExistsException If a pending contact with the same
|
||||
* handshake public key already exists
|
||||
*/
|
||||
PendingContact addPendingContact(Transaction txn, String link, String alias)
|
||||
throws DbException, FormatException, GeneralSecurityException,
|
||||
ContactExistsException, PendingContactExistsException;
|
||||
|
||||
/**
|
||||
* Creates a {@link PendingContact} from the given handshake link and
|
||||
* alias, adds it to the database and returns it.
|
||||
@@ -140,11 +166,24 @@ public interface ContactManager {
|
||||
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns a list of {@link PendingContact PendingContacts} and their
|
||||
* {@link PendingContactState states}.
|
||||
*/
|
||||
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a {@link PendingContact}.
|
||||
*/
|
||||
void removePendingContact(PendingContactId p) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a {@link PendingContact}.
|
||||
*/
|
||||
void removePendingContact(Transaction txn, PendingContactId p)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the contact with the given ID.
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.contact;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
@@ -17,9 +16,4 @@ public class PendingContactId extends UniqueId {
|
||||
public PendingContactId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
return o instanceof PendingContactId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -10,6 +11,8 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface CryptoComponent {
|
||||
|
||||
UniqueId generateUniqueId();
|
||||
|
||||
SecretKey generateSecretKey();
|
||||
|
||||
SecureRandom getSecureRandom();
|
||||
@@ -172,9 +175,11 @@ public interface CryptoComponent {
|
||||
String asciiArmour(byte[] b, int lineLength);
|
||||
|
||||
/**
|
||||
* Encode the onion/hidden service address given its public key. As
|
||||
* specified here: https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
|
||||
* Encode the Onion given its public key. Specified here:
|
||||
* https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
|
||||
*
|
||||
* @return the encoded onion, base32 chars
|
||||
*/
|
||||
String encodeOnionAddress(byte[] publicKey);
|
||||
String encodeOnion(byte[] publicKey);
|
||||
|
||||
}
|
||||
|
||||
@@ -471,6 +471,14 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
||||
ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Resets the transmission count, expiry time and max latency of all messages
|
||||
* that are eligible to be sent to the given contact. This includes messages
|
||||
* that have already been sent and are not yet due for retransmission.
|
||||
*/
|
||||
void resetUnackedMessagesToSend(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the total length, including headers, of all messages that are
|
||||
* eligible to be sent to the given contact. This may include messages
|
||||
|
||||
@@ -21,9 +21,4 @@ public class AuthorId extends UniqueId {
|
||||
public AuthorId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof AuthorId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public class InvalidMailboxIdException extends Exception {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class MailboxAuthToken extends MailboxId {
|
||||
public MailboxAuthToken(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MailboxAuthToken} from the given string.
|
||||
*
|
||||
* @throws InvalidMailboxIdException if token is not valid.
|
||||
*/
|
||||
public static MailboxAuthToken fromString(@Nullable String token)
|
||||
throws InvalidMailboxIdException {
|
||||
return new MailboxAuthToken(bytesFromString(token));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class MailboxFileId extends MailboxId {
|
||||
public MailboxFileId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MailboxFileId} from the given string.
|
||||
*
|
||||
* @throws IllegalArgumentException if token is not valid.
|
||||
*/
|
||||
public static MailboxFileId fromString(@Nullable String token)
|
||||
throws InvalidMailboxIdException {
|
||||
return new MailboxFileId(bytesFromString(token));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public class MailboxFolderId extends MailboxId {
|
||||
public MailboxFolderId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MailboxFolderId} from the given string.
|
||||
*
|
||||
* @throws IllegalArgumentException if token is not valid.
|
||||
*/
|
||||
public static MailboxFolderId fromString(@Nullable String token)
|
||||
throws InvalidMailboxIdException {
|
||||
return new MailboxFolderId(bytesFromString(token));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
||||
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
public abstract class MailboxId extends UniqueId {
|
||||
MailboxId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns valid {@link MailboxId} bytes from the given string.
|
||||
*
|
||||
* @throws InvalidMailboxIdException if token is not valid.
|
||||
*/
|
||||
static byte[] bytesFromString(@Nullable String token)
|
||||
throws InvalidMailboxIdException {
|
||||
if (token == null || token.length() != 64) {
|
||||
throw new InvalidMailboxIdException();
|
||||
}
|
||||
try {
|
||||
return fromHexString(token);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidMailboxIdException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation expected by the mailbox API.
|
||||
* Also used for serialization.
|
||||
*/
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return toHexString(getBytes()).toLowerCase(Locale.US);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface MailboxManager {
|
||||
|
||||
/**
|
||||
* @return true if a mailbox is already paired.
|
||||
*/
|
||||
boolean isPaired(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* @return the current status of the mailbox.
|
||||
*/
|
||||
MailboxStatus getMailboxStatus(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the currently running pairing task,
|
||||
* or null if no pairing task is running.
|
||||
*/
|
||||
@Nullable
|
||||
MailboxPairingTask getCurrentPairingTask();
|
||||
|
||||
/**
|
||||
* Starts and returns a pairing task. If a pairing task is already running,
|
||||
* it will be returned and the argument will be ignored.
|
||||
*
|
||||
* @param qrCodePayload The ISO-8859-1 encoded bytes of the mailbox QR code.
|
||||
*/
|
||||
MailboxPairingTask startPairingTask(String qrCodePayload);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
public abstract class MailboxPairingState {
|
||||
|
||||
public static class QrCodeReceived extends MailboxPairingState {
|
||||
}
|
||||
|
||||
public static class Pairing extends MailboxPairingState {
|
||||
}
|
||||
|
||||
public static class Paired extends MailboxPairingState {
|
||||
}
|
||||
|
||||
public static class InvalidQrCode extends MailboxPairingState {
|
||||
}
|
||||
|
||||
public static class MailboxAlreadyPaired extends MailboxPairingState {
|
||||
}
|
||||
|
||||
public static class ConnectionError extends MailboxPairingState {
|
||||
}
|
||||
|
||||
public static class UnexpectedError extends MailboxPairingState {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface MailboxPairingTask extends Runnable {
|
||||
|
||||
/**
|
||||
* Adds an observer to the task. The observer will be notified on the
|
||||
* event thread of the current state of the task and any subsequent state
|
||||
* changes.
|
||||
*/
|
||||
void addObserver(Consumer<MailboxPairingState> observer);
|
||||
|
||||
/**
|
||||
* Removes an observer from the task.
|
||||
*/
|
||||
void removeObserver(Consumer<MailboxPairingState> observer);
|
||||
|
||||
}
|
||||
@@ -8,21 +8,27 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class MailboxProperties {
|
||||
|
||||
private final String onionAddress, authToken;
|
||||
private final String baseUrl;
|
||||
private final MailboxAuthToken authToken;
|
||||
private final boolean owner;
|
||||
|
||||
public MailboxProperties(String onionAddress, String authToken,
|
||||
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||
boolean owner) {
|
||||
this.onionAddress = onionAddress;
|
||||
this.baseUrl = baseUrl;
|
||||
this.authToken = authToken;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public String getOnionAddress() {
|
||||
return onionAddress;
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public String getAuthToken() {
|
||||
public String getOnion() {
|
||||
return baseUrl.replaceFirst("^http://", "")
|
||||
.replaceFirst("\\.onion$", "");
|
||||
}
|
||||
|
||||
public MailboxAuthToken getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class MailboxPropertiesUpdate {
|
||||
|
||||
private final String onion;
|
||||
private final MailboxAuthToken authToken;
|
||||
private final MailboxFolderId inboxId;
|
||||
private final MailboxFolderId outboxId;
|
||||
|
||||
public MailboxPropertiesUpdate(String onion,
|
||||
MailboxAuthToken authToken, MailboxFolderId inboxId,
|
||||
MailboxFolderId outboxId) {
|
||||
this.onion = onion;
|
||||
this.authToken = authToken;
|
||||
this.inboxId = inboxId;
|
||||
this.outboxId = outboxId;
|
||||
}
|
||||
|
||||
public String getOnion() {
|
||||
return onion;
|
||||
}
|
||||
|
||||
public MailboxAuthToken getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
public MailboxFolderId getInboxId() {
|
||||
return inboxId;
|
||||
}
|
||||
|
||||
public MailboxFolderId getOutboxId() {
|
||||
return outboxId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface MailboxPropertyManager {
|
||||
|
||||
/**
|
||||
* The unique ID of the mailbox property client.
|
||||
*/
|
||||
ClientId CLIENT_ID =
|
||||
new ClientId("org.briarproject.bramble.mailbox.properties");
|
||||
|
||||
/**
|
||||
* The current major version of the mailbox property client.
|
||||
*/
|
||||
int MAJOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The current minor version of the mailbox property client.
|
||||
*/
|
||||
int MINOR_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The number of properties required for a (non-empty) update message.
|
||||
*/
|
||||
int PROP_COUNT = 4;
|
||||
|
||||
/**
|
||||
* The required properties of a non-empty update message.
|
||||
*/
|
||||
String PROP_KEY_ONION = "onion";
|
||||
String PROP_KEY_AUTHTOKEN = "authToken";
|
||||
String PROP_KEY_INBOXID = "inboxId";
|
||||
String PROP_KEY_OUTBOXID = "outboxId";
|
||||
|
||||
/**
|
||||
* Length of the Onion property.
|
||||
*/
|
||||
int PROP_ONION_LENGTH = 56;
|
||||
|
||||
/**
|
||||
* Message metadata key for the version number of a local or remote update,
|
||||
* as a BDF long.
|
||||
*/
|
||||
String MSG_KEY_VERSION = "version";
|
||||
|
||||
/**
|
||||
* Message metadata key for whether an update is local or remote, as a BDF
|
||||
* boolean.
|
||||
*/
|
||||
String MSG_KEY_LOCAL = "local";
|
||||
|
||||
@Nullable
|
||||
MailboxPropertiesUpdate getLocalProperties(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
@Nullable
|
||||
MailboxPropertiesUpdate getRemoteProperties(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -9,6 +12,13 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface MailboxSettingsManager {
|
||||
|
||||
/**
|
||||
* Registers a hook to be called when a mailbox has been paired or unpaired.
|
||||
* This method should be called before
|
||||
* {@link LifecycleManager#startServices(SecretKey)}.
|
||||
*/
|
||||
void registerMailboxHook(MailboxHook hook);
|
||||
|
||||
@Nullable
|
||||
MailboxProperties getOwnMailboxProperties(Transaction txn)
|
||||
throws DbException;
|
||||
@@ -23,4 +33,28 @@ public interface MailboxSettingsManager {
|
||||
|
||||
void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||
throws DbException;
|
||||
|
||||
void setPendingUpload(Transaction txn, ContactId id,
|
||||
@Nullable String filename) throws DbException;
|
||||
|
||||
@Nullable
|
||||
String getPendingUpload(Transaction txn, ContactId id) throws DbException;
|
||||
|
||||
interface MailboxHook {
|
||||
/**
|
||||
* Called when Briar is paired with a mailbox
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
* @param ownOnion Our new mailbox's onion (56 base32 chars)
|
||||
*/
|
||||
void mailboxPaired(Transaction txn, String ownOnion)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Called when the mailbox is unpaired
|
||||
*
|
||||
* @param txn A read-write transaction
|
||||
*/
|
||||
void mailboxUnpaired(Transaction txn) throws DbException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast by {@link MailboxSettingsManager} when
|
||||
* recording the connection status of own Mailbox.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class OwnMailboxConnectionStatusEvent extends Event {
|
||||
|
||||
private final MailboxStatus status;
|
||||
|
||||
public OwnMailboxConnectionStatusEvent(MailboxStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public MailboxStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.api.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when {@link MailboxPropertiesUpdate} are received
|
||||
* from a contact.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class RemoteMailboxPropertiesUpdateEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
@Nullable
|
||||
private final MailboxPropertiesUpdate mailboxPropertiesUpdate;
|
||||
|
||||
public RemoteMailboxPropertiesUpdateEvent(ContactId contactId,
|
||||
@Nullable MailboxPropertiesUpdate mailboxPropertiesUpdate) {
|
||||
this.contactId = contactId;
|
||||
this.mailboxPropertiesUpdate = mailboxPropertiesUpdate;
|
||||
}
|
||||
|
||||
public ContactId getContact() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate getMailboxPropertiesUpdate() {
|
||||
return mailboxPropertiesUpdate;
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,4 @@ public class GroupId extends UniqueId {
|
||||
public GroupId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof GroupId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,4 @@ public class MessageId extends UniqueId {
|
||||
public MessageId(byte[] id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof MessageId && super.equals(o);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +113,25 @@ public interface KeyManager {
|
||||
/**
|
||||
* Looks up the given tag and returns a {@link StreamContext} for reading
|
||||
* from the corresponding stream, or null if an error occurs or the tag was
|
||||
* unexpected.
|
||||
* unexpected. Marks the tag as recognised and updates the reordering
|
||||
* window.
|
||||
*/
|
||||
@Nullable
|
||||
StreamContext getStreamContext(TransportId t, byte[] tag)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Looks up the given tag and returns a {@link StreamContext} for reading
|
||||
* from the corresponding stream, or null if an error occurs or the tag was
|
||||
* unexpected. Only returns the StreamContext; does not mark the tag as
|
||||
* recognised.
|
||||
*/
|
||||
@Nullable
|
||||
StreamContext getStreamContextOnly(TransportId t, byte[] tag)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the tag as recognised and updates the reordering window.
|
||||
*/
|
||||
void markTagAsRecognised(TransportId t, byte[] tag) throws DbException;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class NetworkUtils {
|
||||
// Despite what the docs say, the return value can be null
|
||||
//noinspection ConstantConditions
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
} catch (SocketException | NullPointerException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,46 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
|
||||
public abstract class BrambleTestCase {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(BrambleTestCase.class.getName());
|
||||
|
||||
@Nullable
|
||||
protected volatile Throwable exceptionInBackgroundThread = null;
|
||||
|
||||
public BrambleTestCase() {
|
||||
// Ensure exceptions thrown on worker threads cause tests to fail
|
||||
UncaughtExceptionHandler fail = (thread, throwable) -> {
|
||||
throwable.printStackTrace();
|
||||
fail();
|
||||
LOG.log(WARNING, "Caught unhandled exception", throwable);
|
||||
exceptionInBackgroundThread = throwable;
|
||||
};
|
||||
Thread.setDefaultUncaughtExceptionHandler(fail);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeBrambleTestCase() {
|
||||
exceptionInBackgroundThread = null;
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterBrambleTestCase() {
|
||||
Throwable thrown = exceptionInBackgroundThread;
|
||||
if (thrown != null) {
|
||||
LOG.log(WARNING,
|
||||
"Background thread has thrown an exception unexpectedly",
|
||||
thrown);
|
||||
throw new AssertionError(thrown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,15 @@ import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
|
||||
import org.briarproject.bramble.api.db.CommitAction;
|
||||
import org.briarproject.bramble.api.db.EventAction;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
@@ -25,7 +30,11 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -35,6 +44,8 @@ import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
|
||||
@@ -45,6 +56,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
|
||||
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
|
||||
public class TestUtils {
|
||||
@@ -209,6 +221,24 @@ public class TestUtils {
|
||||
getAgreementPublicKey(), verified);
|
||||
}
|
||||
|
||||
public static void writeBytes(File file, byte[] bytes)
|
||||
throws IOException {
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
//noinspection TryFinallyCanBeTryWithResources
|
||||
try {
|
||||
outputStream.write(bytes);
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readBytes(File file) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
FileInputStream inputStream = new FileInputStream(file);
|
||||
copyAndClose(inputStream, outputStream);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
public static double getMedian(Collection<? extends Number> samples) {
|
||||
int size = samples.size();
|
||||
if (size == 0) throw new IllegalArgumentException();
|
||||
@@ -248,4 +278,27 @@ public class TestUtils {
|
||||
return optionalTests != null &&
|
||||
asList(optionalTests.split(",")).contains(testClass.getName());
|
||||
}
|
||||
|
||||
public static boolean mailboxPropertiesUpdateEqual(
|
||||
@Nullable MailboxPropertiesUpdate a,
|
||||
@Nullable MailboxPropertiesUpdate b) {
|
||||
if (a == null || b == null) {
|
||||
return a == b;
|
||||
}
|
||||
return a.getOnion().equals(b.getOnion()) &&
|
||||
a.getAuthToken().equals(b.getAuthToken()) &&
|
||||
a.getInboxId().equals(b.getInboxId()) &&
|
||||
a.getOutboxId().equals(b.getOutboxId());
|
||||
}
|
||||
|
||||
public static boolean hasEvent(Transaction txn,
|
||||
Class<? extends Event> eventClass) {
|
||||
for (CommitAction action : txn.getActions()) {
|
||||
if (action instanceof EventAction) {
|
||||
Event event = ((EventAction) action).getEvent();
|
||||
if (eventClass.isInstance(event)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ThreadExceptionTest extends BrambleTestCase {
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void testAssertionErrorMakesTestCaseFail() {
|
||||
// This is what BrambleTestCase does, too:
|
||||
fail();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionInThreadMakesTestCaseFail() {
|
||||
Thread t = new Thread(() -> {
|
||||
System.out.println("thread before exception");
|
||||
throw new RuntimeException("boom");
|
||||
});
|
||||
|
||||
t.start();
|
||||
try {
|
||||
t.join();
|
||||
System.out.println("joined thread");
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("interrupted while joining thread");
|
||||
fail();
|
||||
}
|
||||
|
||||
assertNotNull(exceptionInBackgroundThread);
|
||||
exceptionInBackgroundThread = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
|
||||
'com.fasterxml.jackson.core:jackson-annotations:2.13.0:jackson-annotations-2.13.0.jar:81f9724d8843e8b08f8f6c0609e7a2b030d00c34861c4ac7e2099a7235047d6f',
|
||||
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
|
||||
|
||||
@@ -10,13 +10,18 @@ apply from: '../dagger.gradle'
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-api', configuration: 'default')
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15to18:1.70'
|
||||
//noinspection GradleDependency
|
||||
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||
implementation 'org.briarproject:jtorctl:0.3'
|
||||
|
||||
//noinspection GradleDependency
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||
@@ -25,7 +30,8 @@ dependencies {
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-imposters:$jmock_version"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
|
||||
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.mailbox.MailboxModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||
@@ -28,6 +29,8 @@ public interface BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(LifecycleModule.EagerSingletons init);
|
||||
|
||||
void inject(MailboxModule.EagerSingletons init);
|
||||
|
||||
void inject(PluginModule.EagerSingletons init);
|
||||
|
||||
void inject(PropertiesModule.EagerSingletons init);
|
||||
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
|
||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new LifecycleModule.EagerSingletons());
|
||||
c.inject(new MailboxModule.EagerSingletons());
|
||||
c.inject(new RendezvousModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.client;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -22,6 +23,9 @@ import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -29,6 +33,7 @@ import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.Base32;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -39,6 +44,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -46,6 +52,12 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE
|
||||
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_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_COUNT;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_ONION_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
@@ -399,6 +411,35 @@ class ClientHelperImpl implements ClientHelper {
|
||||
return tpMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate parseAndValidateMailboxPropertiesUpdate(
|
||||
BdfDictionary properties) throws FormatException {
|
||||
if (properties.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// Accepting more props than we need, for forward compatibility
|
||||
if (properties.size() < PROP_COUNT) {
|
||||
throw new FormatException();
|
||||
}
|
||||
String onion = properties.getString(PROP_KEY_ONION);
|
||||
checkLength(onion, PROP_ONION_LENGTH);
|
||||
try {
|
||||
Base32.decode(onion, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
byte[] authToken = properties.getRaw(PROP_KEY_AUTHTOKEN);
|
||||
checkLength(authToken, UniqueId.LENGTH);
|
||||
byte[] inboxId = properties.getRaw(PROP_KEY_INBOXID);
|
||||
checkLength(inboxId, UniqueId.LENGTH);
|
||||
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
|
||||
checkLength(outboxId, UniqueId.LENGTH);
|
||||
return new MailboxPropertiesUpdate(onion,
|
||||
new MailboxAuthToken(authToken), new MailboxFolderId(inboxId),
|
||||
new MailboxFolderId(outboxId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException {
|
||||
|
||||
@@ -121,28 +121,40 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
||||
|
||||
@Override
|
||||
public String getHandshakeLink() throws DbException {
|
||||
KeyPair keyPair = db.transactionWithResult(true,
|
||||
identityManager::getHandshakeKeys);
|
||||
return db.transactionWithResult(true, this::getHandshakeLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandshakeLink(Transaction txn) throws DbException {
|
||||
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
|
||||
return pendingContactFactory.createHandshakeLink(keyPair.getPublic());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingContact addPendingContact(Transaction txn, String link,
|
||||
String alias)
|
||||
throws DbException, FormatException, GeneralSecurityException {
|
||||
PendingContact p =
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
AuthorId local = identityManager.getLocalAuthor(txn).getId();
|
||||
db.addPendingContact(txn, p, local);
|
||||
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
||||
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
||||
ourKeyPair);
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingContact addPendingContact(String link, String alias)
|
||||
throws DbException, FormatException, GeneralSecurityException {
|
||||
PendingContact p =
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
AuthorId local = identityManager.getLocalAuthor(txn).getId();
|
||||
db.addPendingContact(txn, p, local);
|
||||
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
||||
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
||||
ourKeyPair);
|
||||
PendingContact p = addPendingContact(txn, link, alias);
|
||||
db.commitTransaction(txn);
|
||||
return p;
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,8 +166,14 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
||||
@Override
|
||||
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
||||
throws DbException {
|
||||
Collection<PendingContact> pendingContacts =
|
||||
db.transactionWithResult(true, db::getPendingContacts);
|
||||
return db.transactionWithResult(true, this::getPendingContacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(
|
||||
Transaction txn)
|
||||
throws DbException {
|
||||
Collection<PendingContact> pendingContacts = db.getPendingContacts(txn);
|
||||
List<Pair<PendingContact, PendingContactState>> pairs =
|
||||
new ArrayList<>(pendingContacts.size());
|
||||
for (PendingContact p : pendingContacts) {
|
||||
@@ -168,7 +186,13 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
||||
|
||||
@Override
|
||||
public void removePendingContact(PendingContactId p) throws DbException {
|
||||
db.transaction(false, txn -> db.removePendingContact(txn, p));
|
||||
db.transaction(false, txn -> removePendingContact(txn, p));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePendingContact(Transaction txn, PendingContactId p)
|
||||
throws DbException {
|
||||
db.removePendingContact(txn, p);
|
||||
states.remove(p);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.bouncycastle.crypto.digests.SHA3Digest;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -41,6 +42,7 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.lang.System.arraycopy;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
|
||||
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
|
||||
@@ -54,7 +56,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
||||
class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CryptoComponentImpl.class.getName());
|
||||
getLogger(CryptoComponentImpl.class.getName());
|
||||
|
||||
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
||||
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
||||
@@ -128,6 +130,13 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniqueId generateUniqueId() {
|
||||
byte[] b = new byte[UniqueId.LENGTH];
|
||||
secureRandom.nextBytes(b);
|
||||
return new UniqueId(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[SecretKey.LENGTH];
|
||||
@@ -449,7 +458,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeOnionAddress(byte[] publicKey) {
|
||||
public String encodeOnion(byte[] publicKey) {
|
||||
Digest digest = new SHA3Digest(256);
|
||||
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
|
||||
digest.update(label, 0, label.length);
|
||||
|
||||
@@ -757,6 +757,14 @@ interface Database<T> {
|
||||
*/
|
||||
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Resets the transmission count, expiry time and max latency of all
|
||||
* messages that are eligible to be sent to the given contact. This includes
|
||||
* messages that have already been sent and are not yet due for
|
||||
* retransmission.
|
||||
*/
|
||||
void resetUnackedMessagesToSend(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the cleanup timer duration for the given message. This does not
|
||||
* start the message's cleanup timer.
|
||||
@@ -841,12 +849,14 @@ interface Database<T> {
|
||||
void stopCleanupTimer(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the transmission count, expiry time and estimated time of arrival
|
||||
* of the given message with respect to the given contact, using the latency
|
||||
* of the transport over which it was sent.
|
||||
* Updates the transmission count, expiry time and max latency of the given
|
||||
* message with respect to the given contact.
|
||||
*
|
||||
* @param maxLatency latency of the transport over which the message was
|
||||
* sent.
|
||||
*/
|
||||
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, long maxLatency)
|
||||
throws DbException;
|
||||
void updateRetransmissionData(T txn, ContactId c, MessageId m,
|
||||
long maxLatency) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
|
||||
@@ -437,7 +437,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
@@ -462,7 +462,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
sentIds.add(m);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
}
|
||||
}
|
||||
if (messages.isEmpty()) return messages;
|
||||
@@ -483,7 +483,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
|
||||
if (ids.isEmpty()) return null;
|
||||
for (MessageId m : ids)
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
return new Offer(ids);
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
@@ -750,6 +750,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getUnackedMessagesToSend(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetUnackedMessagesToSend(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
db.resetUnackedMessagesToSend(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnackedMessageBytesToSend(Transaction transaction,
|
||||
ContactId c) throws DbException {
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
|
||||
interface DatabaseConstants {
|
||||
|
||||
/**
|
||||
@@ -25,19 +23,6 @@ interface DatabaseConstants {
|
||||
*/
|
||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the time of the last database
|
||||
* compaction is stored.
|
||||
*/
|
||||
String LAST_COMPACTED_KEY = "lastCompacted";
|
||||
|
||||
/**
|
||||
* The maximum time between database compactions in milliseconds. When the
|
||||
* database is opened it will be compacted if more than this amount of time
|
||||
* has passed since the last compaction.
|
||||
*/
|
||||
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the flag is stored indicating
|
||||
* whether the database is marked as dirty.
|
||||
|
||||
@@ -85,12 +85,17 @@ class H2Database extends JdbcDatabase {
|
||||
public void close() throws DbException {
|
||||
// H2 will close the database when the last connection closes
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
c = createConnection();
|
||||
super.closeAllConnections();
|
||||
closeAllConnections();
|
||||
setDirty(c, false);
|
||||
s = c.createStatement();
|
||||
s.execute("SHUTDOWN COMPACT");
|
||||
s.close();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
tryToClose(c, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
|
||||
@@ -79,11 +79,11 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
super.closeAllConnections();
|
||||
closeAllConnections();
|
||||
c = createConnection();
|
||||
setDirty(c, false);
|
||||
s = c.createStatement();
|
||||
s.executeQuery("SHUTDOWN");
|
||||
s.executeQuery("SHUTDOWN COMPACT");
|
||||
s.close();
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
@@ -106,7 +106,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
Connection c = null;
|
||||
Statement s = null;
|
||||
try {
|
||||
super.closeAllConnections();
|
||||
closeAllConnections();
|
||||
c = createConnection();
|
||||
s = c.createStatement();
|
||||
s.executeQuery("SHUTDOWN COMPACT");
|
||||
|
||||
@@ -85,8 +85,6 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
@@ -102,7 +100,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 49;
|
||||
static final int CODE_SCHEMA_VERSION = 50;
|
||||
|
||||
// Time period offsets for incoming transport keys
|
||||
private static final int OFFSET_PREV = -1;
|
||||
@@ -252,7 +250,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
+ " expiry BIGINT NOT NULL,"
|
||||
+ " txCount INT NOT NULL,"
|
||||
+ " eta BIGINT NOT NULL,"
|
||||
+ " maxLatency BIGINT," // Null if latency was reset
|
||||
+ " PRIMARY KEY (messageId, contactId),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
@@ -378,8 +376,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
throws DbException, SQLException;
|
||||
|
||||
// Used exclusively during open to compact the database after schema
|
||||
// migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
|
||||
// elapsed
|
||||
// migrations or if the database was not shut down cleanly
|
||||
protected abstract void compactAndClose() throws DbException;
|
||||
|
||||
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||
@@ -405,7 +402,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (reopen) {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
wasDirtyOnInitialisation = isDirty(s);
|
||||
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
|
||||
boolean migrated = migrateSchema(txn, s, listener);
|
||||
compact = wasDirtyOnInitialisation || migrated;
|
||||
} else {
|
||||
wasDirtyOnInitialisation = false;
|
||||
createTables(txn);
|
||||
@@ -429,16 +427,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
compactAndClose();
|
||||
logDuration(LOG, "Compacting database", start);
|
||||
// Allow the next transaction to reopen the DB
|
||||
synchronized (connectionsLock) {
|
||||
closed = false;
|
||||
}
|
||||
txn = startTransaction();
|
||||
connectionsLock.lock();
|
||||
try {
|
||||
storeLastCompacted(txn);
|
||||
commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
abortTransaction(txn);
|
||||
throw e;
|
||||
closed = false;
|
||||
} finally {
|
||||
connectionsLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,18 +492,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
new Migration45_46(),
|
||||
new Migration46_47(dbTypes),
|
||||
new Migration47_48(),
|
||||
new Migration48_49()
|
||||
new Migration48_49(),
|
||||
new Migration49_50()
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isCompactionDue(Settings s) {
|
||||
long lastCompacted = s.getLong(LAST_COMPACTED_KEY, 0);
|
||||
long elapsed = clock.currentTimeMillis() - lastCompacted;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(elapsed + " ms since last compaction");
|
||||
return elapsed > MAX_COMPACTION_INTERVAL_MS;
|
||||
}
|
||||
|
||||
private void storeSchemaVersion(Connection txn, int version)
|
||||
throws DbException {
|
||||
Settings s = new Settings();
|
||||
@@ -518,12 +504,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
private void storeLastCompacted(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
private boolean isDirty(Settings s) {
|
||||
return s.getBoolean(DIRTY_KEY, false);
|
||||
}
|
||||
@@ -537,7 +517,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private void initialiseSettings(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
||||
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
@@ -917,9 +896,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
||||
+ " timestamp, length, state, groupShared, messageShared,"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount, eta)"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount,"
|
||||
+ " maxLatency)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0,"
|
||||
+ " 0)";
|
||||
+ " NULL)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1153,17 +1133,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
} else {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)";
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
}
|
||||
rs = ps.executeQuery();
|
||||
boolean messagesToSend = rs.next();
|
||||
@@ -2191,7 +2171,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getMessagesToOffer(Connection txn,
|
||||
ContactId c, int maxMessages, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2200,13 +2179,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " ORDER BY timestamp LIMIT ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
ps.setInt(5, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
@@ -2250,7 +2230,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
|
||||
int maxLength, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2259,13 +2238,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " ORDER BY timestamp";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
@@ -2549,7 +2529,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
||||
ContactId c, int maxLength, long maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2558,13 +2537,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = TRUE"
|
||||
+ " AND (expiry <= ? OR eta > ?)"
|
||||
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||
+ " OR ? < maxLatency)"
|
||||
+ " ORDER BY timestamp";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
ps.setLong(4, maxLatency);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
@@ -3290,6 +3270,30 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetUnackedMessagesToSend(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE statuses SET expiry = 0, txCount = 0,"
|
||||
+ " maxLatency = NULL"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0) {
|
||||
throw new DbStateException();
|
||||
}
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCleanupTimerDuration(Connection txn, MessageId m,
|
||||
long duration) throws DbException {
|
||||
@@ -3617,8 +3621,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
|
||||
long maxLatency) throws DbException {
|
||||
public void updateRetransmissionData(Connection txn, ContactId c,
|
||||
MessageId m, long maxLatency) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -3634,13 +3638,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
rs.close();
|
||||
ps.close();
|
||||
sql = "UPDATE statuses"
|
||||
+ " SET expiry = ?, txCount = txCount + 1, eta = ?"
|
||||
+ " SET expiry = ?, txCount = txCount + 1, maxLatency = ?"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
|
||||
ps.setLong(2, eta);
|
||||
ps.setLong(2, maxLatency);
|
||||
ps.setBytes(3, m.getBytes());
|
||||
ps.setInt(4, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
|
||||
class Migration49_50 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG = getLogger(Migration49_50.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 49;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.execute("ALTER TABLE statuses"
|
||||
+ " ALTER COLUMN eta"
|
||||
+ " RENAME TO maxLatency");
|
||||
s.execute("ALTER TABLE statuses"
|
||||
+ " ALTER COLUMN maxLatency"
|
||||
+ " SET NULL");
|
||||
s.execute("UPDATE statuses SET maxLatency = NULL");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,49 @@
|
||||
package org.briarproject.bramble.io;
|
||||
|
||||
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import okhttp3.Dns;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
@Module
|
||||
public class IoModule {
|
||||
|
||||
private static final int CONNECT_TIMEOUT = 60_000; // Milliseconds
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
|
||||
return timeoutMonitor;
|
||||
}
|
||||
|
||||
// Share an HTTP client instance between requests where possible, while
|
||||
// allowing the client to be garbage-collected between requests. The
|
||||
// provider keeps a weak reference to the last client instance and reuses
|
||||
// the instance until it gets garbage-collected. See
|
||||
// https://medium.com/@leandromazzuquini/if-you-are-using-okhttp-you-should-know-this-61d68e065a2b
|
||||
@Provides
|
||||
@Singleton
|
||||
WeakSingletonProvider<OkHttpClient> provideOkHttpClientProvider(
|
||||
SocketFactory torSocketFactory, Dns noDnsLookups) {
|
||||
return new WeakSingletonProvider<OkHttpClient>() {
|
||||
@Override
|
||||
@Nonnull
|
||||
public OkHttpClient createInstance() {
|
||||
return new OkHttpClient.Builder()
|
||||
.socketFactory(torSocketFactory)
|
||||
.dns(noDnsLookups) // Don't make local DNS lookups
|
||||
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
interface MailboxApi {
|
||||
|
||||
/**
|
||||
* Sets up the mailbox with the setup token.
|
||||
*
|
||||
* @param properties MailboxProperties with the setup token
|
||||
* @return the owner token
|
||||
* @throws ApiException for 401 response.
|
||||
*/
|
||||
MailboxAuthToken setup(MailboxProperties properties)
|
||||
throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Checks the status of the mailbox.
|
||||
*
|
||||
* @return true if the status is OK, false otherwise.
|
||||
* @throws ApiException for 401 response.
|
||||
*/
|
||||
boolean checkStatus(MailboxProperties properties)
|
||||
throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Unpairs Briar and the mailbox (owner only).
|
||||
* Resets mailbox state to that after first install
|
||||
* (e.g. removes all stored files as well).
|
||||
*/
|
||||
void wipeMailbox(MailboxProperties properties)
|
||||
throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Adds a new contact to the mailbox.
|
||||
*
|
||||
* @throws TolerableFailureException if response code is 409
|
||||
* (contact was already added).
|
||||
*/
|
||||
void addContact(MailboxProperties properties, MailboxContact contact)
|
||||
throws IOException, ApiException, TolerableFailureException;
|
||||
|
||||
/**
|
||||
* Deletes a contact from the mailbox.
|
||||
* This should get called after a contact was removed from Briar.
|
||||
*
|
||||
* @throws TolerableFailureException if response code is 404
|
||||
* (contact probably was already deleted).
|
||||
*/
|
||||
void deleteContact(MailboxProperties properties, ContactId contactId)
|
||||
throws IOException, ApiException, TolerableFailureException;
|
||||
|
||||
/**
|
||||
* Gets a list of {@link ContactId}s from the mailbox.
|
||||
* These are the contacts that the mailbox already knows about.
|
||||
*/
|
||||
Collection<ContactId> getContacts(MailboxProperties properties)
|
||||
throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Used by contacts to send files to the owner
|
||||
* and by the owner to send files to contacts.
|
||||
* <p>
|
||||
* The owner can add files to the contacts' inboxes
|
||||
* and the contacts can add files to their own outbox.
|
||||
*/
|
||||
void addFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
File file) throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Used by owner and contacts to list their files to retrieve.
|
||||
* <p>
|
||||
* Returns 200 OK with the list of files in JSON.
|
||||
*/
|
||||
List<MailboxFile> getFiles(MailboxProperties properties,
|
||||
MailboxFolderId folderId) throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Used by owner and contacts to retrieve a file.
|
||||
* <p>
|
||||
* Returns 200 OK if successful with the files' raw bytes
|
||||
* in the response body.
|
||||
*
|
||||
* @param file the empty file the response bytes will be written into.
|
||||
*/
|
||||
void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
MailboxFileId fileId, File file) throws IOException, ApiException;
|
||||
|
||||
/**
|
||||
* Used by owner and contacts to delete files.
|
||||
* <p>
|
||||
* Returns 200 OK (no exception) if deletion was successful.
|
||||
*
|
||||
* @throws TolerableFailureException on 404 response,
|
||||
* because file was most likely deleted already.
|
||||
*/
|
||||
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
MailboxFileId fileId)
|
||||
throws IOException, ApiException, TolerableFailureException;
|
||||
|
||||
/**
|
||||
* Lists all contact outboxes that have files available
|
||||
* for the owner to download.
|
||||
*
|
||||
* @return a list of folder names
|
||||
* to be used with {@link #getFiles(MailboxProperties, MailboxFolderId)}.
|
||||
* @throws IllegalArgumentException if used by non-owner.
|
||||
*/
|
||||
List<MailboxFolderId> getFolders(MailboxProperties properties)
|
||||
throws IOException, ApiException;
|
||||
|
||||
@Immutable
|
||||
@JsonSerialize
|
||||
class MailboxContact {
|
||||
public final int contactId;
|
||||
public final MailboxAuthToken token;
|
||||
public final MailboxFolderId inboxId, outboxId;
|
||||
|
||||
MailboxContact(ContactId contactId,
|
||||
MailboxAuthToken token,
|
||||
MailboxFolderId inboxId,
|
||||
MailboxFolderId outboxId) {
|
||||
this.contactId = contactId.getInt();
|
||||
this.token = token;
|
||||
this.inboxId = inboxId;
|
||||
this.outboxId = outboxId;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize
|
||||
class MailboxFile implements Comparable<MailboxFile> {
|
||||
public final MailboxFileId name;
|
||||
public final long time;
|
||||
|
||||
public MailboxFile(MailboxFileId name, long time) {
|
||||
this.name = name;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@Nonnull MailboxApi.MailboxFile mailboxFile) {
|
||||
//noinspection UseCompareMethod
|
||||
return time < mailboxFile.time ? -1 :
|
||||
(time == mailboxFile.time ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class ApiException extends Exception {
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class MailboxAlreadyPairedException extends ApiException {
|
||||
}
|
||||
|
||||
/**
|
||||
* A failure that does not need to be retried,
|
||||
* e.g. when adding a contact that already exists.
|
||||
*/
|
||||
@Immutable
|
||||
class TolerableFailureException extends Exception {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static okhttp3.internal.Util.EMPTY_REQUEST;
|
||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class MailboxApiImpl implements MailboxApi {
|
||||
|
||||
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
|
||||
private final JsonMapper mapper = JsonMapper.builder()
|
||||
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
|
||||
.build();
|
||||
private static final MediaType JSON =
|
||||
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
|
||||
private static final MediaType FILE =
|
||||
requireNonNull(MediaType.parse("application/octet-stream"));
|
||||
|
||||
@Inject
|
||||
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
|
||||
this.httpClientProvider = httpClientProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxAuthToken setup(MailboxProperties properties)
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(properties.getBaseUrl() + "/setup")
|
||||
.put(EMPTY_REQUEST)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
Response response = client.newCall(request).execute();
|
||||
if (response.code() == 401) throw new MailboxAlreadyPairedException();
|
||||
if (!response.isSuccessful()) throw new ApiException();
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) throw new ApiException();
|
||||
try {
|
||||
JsonNode node = mapper.readTree(body.string());
|
||||
JsonNode tokenNode = node.get("token");
|
||||
if (tokenNode == null) {
|
||||
throw new ApiException();
|
||||
}
|
||||
String ownerToken = tokenNode.textValue();
|
||||
return MailboxAuthToken.fromString(ownerToken);
|
||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||
throw new ApiException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkStatus(MailboxProperties properties)
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Response response = sendGetRequest(properties, "/status");
|
||||
if (response.code() == 401) throw new ApiException();
|
||||
return response.isSuccessful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wipeMailbox(MailboxProperties properties)
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(properties.getBaseUrl() + "/")
|
||||
.delete()
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
Response response = client.newCall(request).execute();
|
||||
if (response.code() != 204) throw new ApiException();
|
||||
}
|
||||
|
||||
/* Contact Management API (owner only) */
|
||||
|
||||
@Override
|
||||
public void addContact(MailboxProperties properties, MailboxContact contact)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
byte[] bodyBytes = mapper.writeValueAsBytes(contact);
|
||||
RequestBody body = RequestBody.create(JSON, bodyBytes);
|
||||
Response response = sendPostRequest(properties, "/contacts", body);
|
||||
if (response.code() == 409) throw new TolerableFailureException();
|
||||
if (!response.isSuccessful()) throw new ApiException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteContact(MailboxProperties properties, ContactId contactId)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
String url = properties.getBaseUrl() + "/contacts/" +
|
||||
contactId.getInt();
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.delete()
|
||||
.url(url)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
Response response = client.newCall(request).execute();
|
||||
if (response.code() == 404) throw new TolerableFailureException();
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ContactId> getContacts(MailboxProperties properties)
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Response response = sendGetRequest(properties, "/contacts");
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) throw new ApiException();
|
||||
try {
|
||||
JsonNode node = mapper.readTree(body.string());
|
||||
ArrayNode contactsNode = getArray(node, "contacts");
|
||||
List<ContactId> list = new ArrayList<>();
|
||||
for (JsonNode contactNode : contactsNode) {
|
||||
if (!contactNode.isNumber()) throw new ApiException();
|
||||
int id = contactNode.intValue();
|
||||
if (id < 1) throw new ApiException();
|
||||
list.add(new ContactId(id));
|
||||
}
|
||||
return list;
|
||||
} catch (JacksonException e) {
|
||||
throw new ApiException();
|
||||
}
|
||||
}
|
||||
|
||||
/* File Management (owner and contacts) */
|
||||
|
||||
@Override
|
||||
public void addFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
File file) throws IOException, ApiException {
|
||||
String path = "/files/" + folderId;
|
||||
RequestBody body = RequestBody.create(FILE, file);
|
||||
Response response = sendPostRequest(properties, path, body);
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MailboxFile> getFiles(MailboxProperties properties,
|
||||
MailboxFolderId folderId) throws IOException, ApiException {
|
||||
String path = "/files/" + folderId;
|
||||
Response response = sendGetRequest(properties, path);
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) throw new ApiException();
|
||||
try {
|
||||
JsonNode node = mapper.readTree(body.string());
|
||||
ArrayNode filesNode = getArray(node, "files");
|
||||
List<MailboxFile> list = new ArrayList<>();
|
||||
for (JsonNode fileNode : filesNode) {
|
||||
if (!fileNode.isObject()) throw new ApiException();
|
||||
ObjectNode objectNode = (ObjectNode) fileNode;
|
||||
JsonNode nameNode = objectNode.get("name");
|
||||
JsonNode timeNode = objectNode.get("time");
|
||||
if (nameNode == null || !nameNode.isTextual()) {
|
||||
throw new ApiException();
|
||||
}
|
||||
if (timeNode == null || !timeNode.isNumber()) {
|
||||
throw new ApiException();
|
||||
}
|
||||
String name = nameNode.asText();
|
||||
long time = timeNode.asLong();
|
||||
if (time < 1) throw new ApiException();
|
||||
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||
throw new ApiException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
|
||||
MailboxFileId fileId, File file) throws IOException, ApiException {
|
||||
String path = "/files/" + folderId + "/" + fileId;
|
||||
Response response = sendGetRequest(properties, path);
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) throw new ApiException();
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
copyAndClose(body.byteStream(), outputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteFile(MailboxProperties properties,
|
||||
MailboxFolderId folderId, MailboxFileId fileId)
|
||||
throws IOException, ApiException, TolerableFailureException {
|
||||
String path = "/files/" + folderId + "/" + fileId;
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.delete()
|
||||
.url(properties.getBaseUrl() + path)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
Response response = client.newCall(request).execute();
|
||||
if (response.code() == 404) throw new TolerableFailureException();
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MailboxFolderId> getFolders(MailboxProperties properties)
|
||||
throws IOException, ApiException {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
Response response = sendGetRequest(properties, "/folders");
|
||||
if (response.code() != 200) throw new ApiException();
|
||||
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) throw new ApiException();
|
||||
try {
|
||||
JsonNode node = mapper.readTree(body.string());
|
||||
ArrayNode filesNode = getArray(node, "folders");
|
||||
List<MailboxFolderId> list = new ArrayList<>();
|
||||
for (JsonNode fileNode : filesNode) {
|
||||
if (!fileNode.isObject()) throw new ApiException();
|
||||
ObjectNode objectNode = (ObjectNode) fileNode;
|
||||
JsonNode idNode = objectNode.get("id");
|
||||
if (idNode == null || !idNode.isTextual()) {
|
||||
throw new ApiException();
|
||||
}
|
||||
String id = idNode.asText();
|
||||
list.add(MailboxFolderId.fromString(id));
|
||||
}
|
||||
return list;
|
||||
} catch (JacksonException | InvalidMailboxIdException e) {
|
||||
throw new ApiException();
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper Functions */
|
||||
|
||||
private Response sendGetRequest(MailboxProperties properties, String path)
|
||||
throws IOException {
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(properties.getBaseUrl() + path)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
return client.newCall(request).execute();
|
||||
}
|
||||
|
||||
private Response sendPostRequest(MailboxProperties properties, String path,
|
||||
RequestBody body) throws IOException {
|
||||
Request request = getRequestBuilder(properties.getAuthToken())
|
||||
.url(properties.getBaseUrl() + path)
|
||||
.post(body)
|
||||
.build();
|
||||
OkHttpClient client = httpClientProvider.get();
|
||||
return client.newCall(request).execute();
|
||||
}
|
||||
|
||||
private Request.Builder getRequestBuilder(MailboxId token) {
|
||||
return new Request.Builder()
|
||||
.addHeader("Authorization", "Bearer " + token);
|
||||
}
|
||||
|
||||
/* JSON helpers */
|
||||
|
||||
private ArrayNode getArray(JsonNode node, String name) throws ApiException {
|
||||
JsonNode arrayNode = node.get(name);
|
||||
if (arrayNode == null || !arrayNode.isArray()) {
|
||||
throw new ApiException();
|
||||
}
|
||||
return (ArrayNode) arrayNode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxManagerImpl implements MailboxManager {
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final MailboxPairingTaskFactory pairingTaskFactory;
|
||||
private final Object lock = new Object();
|
||||
|
||||
@Nullable
|
||||
@GuardedBy("lock")
|
||||
private MailboxPairingTask pairingTask = null;
|
||||
|
||||
@Inject
|
||||
MailboxManagerImpl(
|
||||
@IoExecutor Executor ioExecutor,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPairingTaskFactory pairingTaskFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.pairingTaskFactory = pairingTaskFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaired(Transaction txn) throws DbException {
|
||||
return mailboxSettingsManager.getOwnMailboxProperties(txn) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxStatus getMailboxStatus(Transaction txn) throws DbException {
|
||||
return mailboxSettingsManager.getOwnMailboxStatus(txn);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MailboxPairingTask getCurrentPairingTask() {
|
||||
synchronized (lock) {
|
||||
return pairingTask;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxPairingTask startPairingTask(String payload) {
|
||||
MailboxPairingTask created;
|
||||
synchronized (lock) {
|
||||
if (pairingTask != null) return pairingTask;
|
||||
created = pairingTaskFactory.createPairingTask(payload);
|
||||
pairingTask = created;
|
||||
}
|
||||
ioExecutor.execute(() -> {
|
||||
created.run();
|
||||
synchronized (lock) {
|
||||
// remove task after it finished
|
||||
pairingTask = null;
|
||||
}
|
||||
});
|
||||
return created;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,86 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MINOR_VERSION;
|
||||
|
||||
@Module
|
||||
public class MailboxModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
MailboxPropertyValidator mailboxPropertyValidator;
|
||||
@Inject
|
||||
MailboxPropertyManager mailboxPropertyManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxManager providesMailboxManager(MailboxManagerImpl mailboxManager) {
|
||||
return mailboxManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxPairingTaskFactory provideMailboxPairingTaskFactory(
|
||||
MailboxPairingTaskFactoryImpl mailboxPairingTaskFactory) {
|
||||
return mailboxPairingTaskFactory;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxSettingsManager provideMailboxSettingsManager(
|
||||
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
||||
return mailboxSettingsManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
|
||||
return mailboxApi;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxPropertyValidator provideMailboxPropertyValidator(
|
||||
ValidationManager validationManager, ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
MailboxPropertyValidator validator = new MailboxPropertyValidator(
|
||||
clientHelper, metadataEncoder, clock);
|
||||
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||
validator);
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MailboxPropertyManager provideMailboxPropertyManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ValidationManager validationManager, ContactManager contactManager,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPropertyManagerImpl mailboxPropertyManager) {
|
||||
lifecycleManager.registerOpenDatabaseHook(mailboxPropertyManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||
mailboxPropertyManager);
|
||||
contactManager.registerContactHook(mailboxPropertyManager);
|
||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||
MINOR_VERSION, mailboxPropertyManager);
|
||||
mailboxSettingsManager.registerMailboxHook(mailboxPropertyManager);
|
||||
return mailboxPropertyManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
interface MailboxPairingTaskFactory {
|
||||
|
||||
MailboxPairingTask createPairingTask(String qrCodePayload);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
|
||||
|
||||
private final Executor eventExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final CryptoComponent crypto;
|
||||
private final Clock clock;
|
||||
private final MailboxApi api;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final MailboxPropertyManager mailboxPropertyManager;
|
||||
|
||||
@Inject
|
||||
MailboxPairingTaskFactoryImpl(
|
||||
@EventExecutor Executor eventExecutor,
|
||||
DatabaseComponent db,
|
||||
CryptoComponent crypto,
|
||||
Clock clock,
|
||||
MailboxApi api,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPropertyManager mailboxPropertyManager) {
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.db = db;
|
||||
this.crypto = crypto;
|
||||
this.clock = clock;
|
||||
this.api = api;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.mailboxPropertyManager = mailboxPropertyManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxPairingTask createPairingTask(String qrCodePayload) {
|
||||
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
|
||||
crypto, clock, api, mailboxSettingsManager,
|
||||
mailboxPropertyManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class MailboxPairingTaskImpl implements MailboxPairingTask {
|
||||
|
||||
private final static Logger LOG =
|
||||
getLogger(MailboxPairingTaskImpl.class.getName());
|
||||
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
|
||||
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||
private static final int VERSION_REQUIRED = 32;
|
||||
|
||||
private final String payload;
|
||||
private final Executor eventExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final CryptoComponent crypto;
|
||||
private final Clock clock;
|
||||
private final MailboxApi api;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final MailboxPropertyManager mailboxPropertyManager;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
private final List<Consumer<MailboxPairingState>> observers =
|
||||
new ArrayList<>();
|
||||
@GuardedBy("lock")
|
||||
private MailboxPairingState state;
|
||||
|
||||
MailboxPairingTaskImpl(
|
||||
String payload,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
DatabaseComponent db,
|
||||
CryptoComponent crypto,
|
||||
Clock clock,
|
||||
MailboxApi api,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
MailboxPropertyManager mailboxPropertyManager) {
|
||||
this.payload = payload;
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.db = db;
|
||||
this.crypto = crypto;
|
||||
this.clock = clock;
|
||||
this.api = api;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.mailboxPropertyManager = mailboxPropertyManager;
|
||||
state = new MailboxPairingState.QrCodeReceived();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addObserver(Consumer<MailboxPairingState> o) {
|
||||
MailboxPairingState state;
|
||||
synchronized (lock) {
|
||||
observers.add(o);
|
||||
state = this.state;
|
||||
eventExecutor.execute(() -> o.accept(state));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObserver(Consumer<MailboxPairingState> o) {
|
||||
synchronized (lock) {
|
||||
observers.remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
pairMailbox();
|
||||
} catch (FormatException e) {
|
||||
onMailboxError(e, new MailboxPairingState.InvalidQrCode());
|
||||
} catch (MailboxAlreadyPairedException e) {
|
||||
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
|
||||
} catch (IOException e) {
|
||||
onMailboxError(e, new MailboxPairingState.ConnectionError());
|
||||
} catch (ApiException | DbException e) {
|
||||
onMailboxError(e, new MailboxPairingState.UnexpectedError());
|
||||
}
|
||||
}
|
||||
|
||||
private void pairMailbox() throws IOException, ApiException, DbException {
|
||||
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
|
||||
setState(new MailboxPairingState.Pairing());
|
||||
MailboxAuthToken ownerToken = api.setup(mailboxProperties);
|
||||
MailboxProperties ownerProperties = new MailboxProperties(
|
||||
mailboxProperties.getBaseUrl(), ownerToken, true);
|
||||
long time = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> {
|
||||
mailboxSettingsManager
|
||||
.setOwnMailboxProperties(txn, ownerProperties);
|
||||
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
|
||||
// A (possibly new) mailbox is paired. Reset message retransmission
|
||||
// timers for contacts who doesn't have their own mailbox. This way,
|
||||
// data stranded on our old mailbox will be re-uploaded to our new.
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
MailboxPropertiesUpdate remoteProps = mailboxPropertyManager
|
||||
.getRemoteProperties(txn, c.getId());
|
||||
if (remoteProps == null) {
|
||||
db.resetUnackedMessagesToSend(txn, c.getId());
|
||||
}
|
||||
}
|
||||
});
|
||||
setState(new MailboxPairingState.Paired());
|
||||
}
|
||||
|
||||
private void onMailboxError(Exception e, MailboxPairingState state) {
|
||||
logException(LOG, WARNING, e);
|
||||
setState(state);
|
||||
}
|
||||
|
||||
private void setState(MailboxPairingState state) {
|
||||
synchronized (lock) {
|
||||
this.state = state;
|
||||
notifyObservers();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void notifyObservers() {
|
||||
List<Consumer<MailboxPairingState>> observers =
|
||||
new ArrayList<>(this.observers);
|
||||
MailboxPairingState state = this.state;
|
||||
eventExecutor.execute(() -> {
|
||||
for (Consumer<MailboxPairingState> o : observers) o.accept(state);
|
||||
});
|
||||
}
|
||||
|
||||
private MailboxProperties decodeQrCodePayload(String payload)
|
||||
throws FormatException {
|
||||
byte[] bytes = payload.getBytes(ISO_8859_1);
|
||||
if (bytes.length != 65) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("QR code length is not 65: " + bytes.length);
|
||||
}
|
||||
throw new FormatException();
|
||||
}
|
||||
int version = bytes[0] & 0xFF;
|
||||
if (version != VERSION_REQUIRED) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("QR code has not version " + VERSION_REQUIRED +
|
||||
": " + version);
|
||||
}
|
||||
throw new FormatException();
|
||||
}
|
||||
LOG.info("QR code is valid");
|
||||
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
||||
String onion = crypto.encodeOnion(onionPubKey);
|
||||
String baseUrl = "http://" + onion + ".onion";
|
||||
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
|
||||
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
|
||||
return new MailboxProperties(baseUrl, setupToken, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
|
||||
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||
|
||||
@NotNullByDefault
|
||||
class MailboxPropertyManagerImpl implements MailboxPropertyManager,
|
||||
OpenDatabaseHook, ContactHook, ClientVersioningHook,
|
||||
IncomingMessageHook, MailboxHook {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final ClientVersioningManager clientVersioningManager;
|
||||
private final MetadataParser metadataParser;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final Clock clock;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
private final CryptoComponent crypto;
|
||||
private final Group localGroup;
|
||||
|
||||
@Inject
|
||||
MailboxPropertyManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
ClientVersioningManager clientVersioningManager,
|
||||
MetadataParser metadataParser,
|
||||
ContactGroupFactory contactGroupFactory, Clock clock,
|
||||
MailboxSettingsManager mailboxSettingsManager,
|
||||
CryptoComponent crypto) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.clientVersioningManager = clientVersioningManager;
|
||||
this.metadataParser = metadataParser;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.clock = clock;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
this.crypto = crypto;
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) {
|
||||
return;
|
||||
}
|
||||
db.addGroup(txn, localGroup);
|
||||
// Set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
addingContact(txn, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||
// Create a group to share with the contact
|
||||
Group g = getContactGroup(c);
|
||||
db.addGroup(txn, g);
|
||||
// Apply the client's visibility to the contact group
|
||||
Visibility client = clientVersioningManager
|
||||
.getClientVisibility(txn, c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||
// Attach the contact ID to the group
|
||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||
// If we are paired, create and send props to the newly added contact
|
||||
MailboxProperties ownProps =
|
||||
mailboxSettingsManager.getOwnMailboxProperties(txn);
|
||||
if (ownProps != null) {
|
||||
createAndSendProperties(txn, c, ownProps.getOnion());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mailboxPaired(Transaction txn, String ownOnion)
|
||||
throws DbException {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
createAndSendProperties(txn, c, ownOnion);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mailboxUnpaired(Transaction txn) throws DbException {
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
sendEmptyProperties(txn, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||
Visibility v) throws DbException {
|
||||
// Apply the client's visibility to the contact group
|
||||
Group g = getContactGroup(c);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||
Metadata meta) throws DbException, InvalidMessageException {
|
||||
try {
|
||||
BdfDictionary d = metadataParser.parse(meta);
|
||||
// Get latest non-local update in the same group (from same contact)
|
||||
LatestUpdate latest = findLatest(txn, m.getGroupId(), false);
|
||||
if (latest != null) {
|
||||
if (d.getLong(MSG_KEY_VERSION) > latest.version) {
|
||||
db.deleteMessage(txn, latest.messageId);
|
||||
db.deleteMessageMetadata(txn, latest.messageId);
|
||||
} else {
|
||||
// Delete this update, we already have a newer one
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
}
|
||||
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
|
||||
MailboxPropertiesUpdate p = parseProperties(body);
|
||||
txn.attach(new RemoteMailboxPropertiesUpdateEvent(c, p));
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return ACCEPT_DO_NOT_SHARE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate getLocalProperties(Transaction txn,
|
||||
ContactId c) throws DbException {
|
||||
return getProperties(txn, db.getContact(txn, c), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MailboxPropertiesUpdate getRemoteProperties(Transaction txn,
|
||||
ContactId c) throws DbException {
|
||||
return getProperties(txn, db.getContact(txn, c), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and sends an update message to the given contact. The message
|
||||
* holds our own mailbox's onion, and generated unique properties. All of
|
||||
* which the contact needs to communicate with our Mailbox.
|
||||
*/
|
||||
private void createAndSendProperties(Transaction txn,
|
||||
Contact c, String ownOnion) throws DbException {
|
||||
MailboxPropertiesUpdate p = new MailboxPropertiesUpdate(ownOnion,
|
||||
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
|
||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
|
||||
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
|
||||
Group g = getContactGroup(c);
|
||||
storeMessageReplaceLatest(txn, g.getId(), p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an empty update message to the given contact. The empty update
|
||||
* indicates for the receiving contact that we no longer have a Mailbox that
|
||||
* they can use.
|
||||
*/
|
||||
private void sendEmptyProperties(Transaction txn, Contact c)
|
||||
throws DbException {
|
||||
Group g = getContactGroup(c);
|
||||
storeMessageReplaceLatest(txn, g.getId(), null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MailboxPropertiesUpdate getProperties(Transaction txn,
|
||||
Contact c, boolean local) throws DbException {
|
||||
MailboxPropertiesUpdate p = null;
|
||||
Group g = getContactGroup(c);
|
||||
try {
|
||||
LatestUpdate latest = findLatest(txn, g.getId(), local);
|
||||
if (latest != null) {
|
||||
BdfList body =
|
||||
clientHelper.getMessageAsList(txn, latest.messageId);
|
||||
p = parseProperties(body);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
|
||||
@Nullable MailboxPropertiesUpdate p) throws DbException {
|
||||
try {
|
||||
LatestUpdate latest = findLatest(txn, g, true);
|
||||
long version = latest == null ? 1 : latest.version + 1;
|
||||
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
|
||||
encodeProperties(version, p));
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_VERSION, version);
|
||||
meta.put(MSG_KEY_LOCAL, true);
|
||||
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||
if (latest != null) {
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
|
||||
throws DbException, FormatException {
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
// We should have at most 1 local and 1 remote
|
||||
if (metadata.size() > 2) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||
BdfDictionary meta = e.getValue();
|
||||
if (meta.getBoolean(MSG_KEY_LOCAL) == local) {
|
||||
return new LatestUpdate(e.getKey(),
|
||||
meta.getLong(MSG_KEY_VERSION));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private BdfList encodeProperties(long version,
|
||||
@Nullable MailboxPropertiesUpdate p) {
|
||||
BdfDictionary dict = new BdfDictionary();
|
||||
if (p != null) {
|
||||
dict.put(PROP_KEY_ONION, p.getOnion());
|
||||
dict.put(PROP_KEY_AUTHTOKEN, p.getAuthToken().getBytes());
|
||||
dict.put(PROP_KEY_INBOXID, p.getInboxId().getBytes());
|
||||
dict.put(PROP_KEY_OUTBOXID, p.getOutboxId().getBytes());
|
||||
}
|
||||
return BdfList.of(version, dict);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MailboxPropertiesUpdate parseProperties(BdfList body)
|
||||
throws FormatException {
|
||||
BdfDictionary dict = body.getDictionary(1);
|
||||
return clientHelper.parseAndValidateMailboxPropertiesUpdate(dict);
|
||||
}
|
||||
|
||||
private Group getContactGroup(Contact c) {
|
||||
return contactGroupFactory.createContactGroup(CLIENT_ID, MAJOR_VERSION,
|
||||
c);
|
||||
}
|
||||
|
||||
private static class LatestUpdate {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final long version;
|
||||
|
||||
private LatestUpdate(MessageId messageId, long version) {
|
||||
this.messageId = messageId;
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.client.BdfMessageValidator;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class MailboxPropertyValidator extends BdfMessageValidator {
|
||||
|
||||
MailboxPropertyValidator(ClientHelper clientHelper,
|
||||
MetadataEncoder metadataEncoder, Clock clock) {
|
||||
super(clientHelper, metadataEncoder, clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws InvalidMessageException, FormatException {
|
||||
// Version, properties
|
||||
checkSize(body, 2);
|
||||
// Version
|
||||
long version = body.getLong(0);
|
||||
if (version < 0) throw new FormatException();
|
||||
// Properties
|
||||
BdfDictionary dictionary = body.getDictionary(1);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(dictionary);
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(MSG_KEY_VERSION, version);
|
||||
meta.put(MSG_KEY_LOCAL, false);
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
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.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -25,14 +33,21 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
|
||||
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
|
||||
static final String SETTINGS_KEY_ATTEMPTS = "attempts";
|
||||
static final String SETTINGS_UPLOADS_NAMESPACE = "mailbox-uploads";
|
||||
|
||||
private final SettingsManager settingsManager;
|
||||
private final List<MailboxHook> hooks = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Inject
|
||||
MailboxSettingsManagerImpl(SettingsManager settingsManager) {
|
||||
this.settingsManager = settingsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerMailboxHook(MailboxHook hook) {
|
||||
hooks.add(hook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailboxProperties getOwnMailboxProperties(Transaction txn)
|
||||
throws DbException {
|
||||
@@ -40,16 +55,24 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
String onion = s.get(SETTINGS_KEY_ONION);
|
||||
String token = s.get(SETTINGS_KEY_TOKEN);
|
||||
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
||||
return new MailboxProperties(onion, token, true);
|
||||
try {
|
||||
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
|
||||
return new MailboxProperties(onion, tokenId, true);
|
||||
} catch (InvalidMailboxIdException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||
throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.put(SETTINGS_KEY_ONION, p.getOnionAddress());
|
||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken());
|
||||
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
|
||||
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
for (MailboxHook hook : hooks) {
|
||||
hook.mailboxPaired(txn, p.getOnion());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,6 +93,8 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||
MailboxStatus status = new MailboxStatus(now, now, 0);
|
||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,10 +102,33 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||
throws DbException {
|
||||
Settings oldSettings =
|
||||
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||
int attempts = oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
|
||||
Settings newSettings = new Settings();
|
||||
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts + 1);
|
||||
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
|
||||
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
||||
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
|
||||
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPendingUpload(Transaction txn, ContactId id,
|
||||
@Nullable String filename) throws DbException {
|
||||
Settings s = new Settings();
|
||||
String value = filename == null ? "" : filename;
|
||||
s.put(String.valueOf(id.getInt()), value);
|
||||
settingsManager.mergeSettings(txn, s, SETTINGS_UPLOADS_NAMESPACE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getPendingUpload(Transaction txn, ContactId id)
|
||||
throws DbException {
|
||||
Settings s =
|
||||
settingsManager.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
|
||||
String filename = s.get(String.valueOf(id.getInt()));
|
||||
if (isNullOrEmpty(filename)) return null;
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.file;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
|
||||
@Deprecated // We can simply remove tasks when they finish
|
||||
@NotNullByDefault
|
||||
interface RemovableDriveTaskRegistry {
|
||||
|
||||
|
||||
@@ -1,40 +1,73 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// TODO: Create a module for this so it doesn't have to be public
|
||||
|
||||
@NotNullByDefault
|
||||
public interface CircumventionProvider {
|
||||
|
||||
enum BridgeType {
|
||||
DEFAULT_OBFS4,
|
||||
NON_DEFAULT_OBFS4,
|
||||
VANILLA,
|
||||
MEEK
|
||||
}
|
||||
|
||||
/**
|
||||
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
|
||||
*
|
||||
* <p>
|
||||
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
|
||||
*/
|
||||
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
|
||||
String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
||||
|
||||
/**
|
||||
* Countries where obfs4 or meek bridge connections are likely to work.
|
||||
* Should be a subset of {@link #BLOCKED}.
|
||||
* Countries where bridge connections are likely to work.
|
||||
* Should be a subset of {@link #BLOCKED} and the union of
|
||||
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
|
||||
* {@link #MEEK_BRIDGES}.
|
||||
*/
|
||||
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };
|
||||
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
|
||||
|
||||
/**
|
||||
* Countries where obfs4 bridges won't work and meek is needed.
|
||||
* Countries where default obfs4 or vanilla bridges are likely to work.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] NEEDS_MEEK = {"CN", "IR"};
|
||||
String[] DEFAULT_BRIDGES = {"EG", "VE"};
|
||||
|
||||
/**
|
||||
* Countries where non-default obfs4 or vanilla bridges are likely to work.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
|
||||
|
||||
/**
|
||||
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
|
||||
* Should be a subset of {@link #BRIDGES}.
|
||||
*/
|
||||
String[] MEEK_BRIDGES = {"CN", "IR"};
|
||||
|
||||
/**
|
||||
* Returns true if vanilla Tor connections are blocked in the given country.
|
||||
*/
|
||||
boolean isTorProbablyBlocked(String countryCode);
|
||||
|
||||
/**
|
||||
* Returns true if bridge connections of some type work in the given
|
||||
* country.
|
||||
*/
|
||||
boolean doBridgesWork(String countryCode);
|
||||
|
||||
boolean needsMeek(String countryCode);
|
||||
/**
|
||||
* Returns the types of bridge connection that are suitable for the given
|
||||
* country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
|
||||
* to work.
|
||||
*/
|
||||
List<BridgeType> getSuitableBridgeTypes(String countryCode);
|
||||
|
||||
@IoExecutor
|
||||
List<String> getBridges(boolean meek);
|
||||
List<String> getBridges(BridgeType type);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -9,24 +10,33 @@ import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class CircumventionProviderImpl implements CircumventionProvider {
|
||||
|
||||
private final static String BRIDGE_FILE_NAME = "bridges";
|
||||
|
||||
private static final Set<String> BLOCKED_IN_COUNTRIES =
|
||||
new HashSet<>(asList(BLOCKED));
|
||||
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
|
||||
private static final Set<String> BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(BRIDGES));
|
||||
private static final Set<String> BRIDGES_NEED_MEEK =
|
||||
new HashSet<>(asList(NEEDS_MEEK));
|
||||
|
||||
@Nullable
|
||||
private volatile List<String> bridges = null;
|
||||
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||
private static final Set<String> MEEK_COUNTRIES =
|
||||
new HashSet<>(asList(MEEK_BRIDGES));
|
||||
|
||||
@Inject
|
||||
CircumventionProviderImpl() {
|
||||
@@ -39,33 +49,40 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
||||
|
||||
@Override
|
||||
public boolean doBridgesWork(String countryCode) {
|
||||
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
|
||||
return BRIDGE_COUNTRIES.contains(countryCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsMeek(String countryCode) {
|
||||
return BRIDGES_NEED_MEEK.contains(countryCode);
|
||||
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
|
||||
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||
return asList(DEFAULT_OBFS4, VANILLA);
|
||||
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||
return asList(NON_DEFAULT_OBFS4, VANILLA);
|
||||
} else if (MEEK_COUNTRIES.contains(countryCode)) {
|
||||
return singletonList(MEEK);
|
||||
} else {
|
||||
return asList(DEFAULT_OBFS4, VANILLA);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@IoExecutor
|
||||
public List<String> getBridges(boolean useMeek) {
|
||||
List<String> bridges = this.bridges;
|
||||
if (bridges != null) return new ArrayList<>(bridges);
|
||||
|
||||
InputStream is = getClass().getClassLoader()
|
||||
.getResourceAsStream(BRIDGE_FILE_NAME);
|
||||
public List<String> getBridges(BridgeType type) {
|
||||
InputStream is = requireNonNull(getClass().getClassLoader()
|
||||
.getResourceAsStream(BRIDGE_FILE_NAME));
|
||||
Scanner scanner = new Scanner(is);
|
||||
|
||||
bridges = new ArrayList<>();
|
||||
List<String> bridges = new ArrayList<>();
|
||||
while (scanner.hasNextLine()) {
|
||||
String line = scanner.nextLine();
|
||||
boolean isMeekBridge = line.startsWith("Bridge meek");
|
||||
if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue;
|
||||
if (!line.startsWith("#")) bridges.add(line);
|
||||
if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
|
||||
(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
|
||||
(type == VANILLA && line.startsWith("v ")) ||
|
||||
(type == MEEK && line.startsWith("m "))) {
|
||||
bridges.add(line.substring(2));
|
||||
}
|
||||
}
|
||||
scanner.close();
|
||||
this.bridges = bridges;
|
||||
return bridges;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
@@ -84,7 +85,6 @@ import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||
@@ -92,6 +92,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
|
||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
@@ -106,7 +109,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
|
||||
"CIRC",
|
||||
"ORCONN",
|
||||
"STATUS_GENERAL",
|
||||
"STATUS_CLIENT",
|
||||
"HS_DESC",
|
||||
"NOTICE",
|
||||
"WARN",
|
||||
"ERR"
|
||||
};
|
||||
private static final String OWNER = "__OwningControllerProcess";
|
||||
private static final int COOKIE_TIMEOUT_MS = 3000;
|
||||
@@ -226,7 +236,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
// Load the settings
|
||||
settings = migrateSettings(callback.getSettings());
|
||||
settings = callback.getSettings();
|
||||
// Install or update the assets if necessary
|
||||
if (!assetsAreUpToDate()) installAssets();
|
||||
if (cookieFile.exists() && !cookieFile.delete())
|
||||
@@ -300,11 +310,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
controlConnection.setEventHandler(this);
|
||||
controlConnection.setEvents(asList(EVENTS));
|
||||
// Check whether Tor has already bootstrapped
|
||||
String phase = controlConnection.getInfo("status/bootstrap-phase");
|
||||
if (phase != null && phase.contains("PROGRESS=100")) {
|
||||
String info = controlConnection.getInfo("status/bootstrap-phase");
|
||||
if (info != null && info.contains("PROGRESS=100")) {
|
||||
LOG.info("Tor has already bootstrapped");
|
||||
state.setBootstrapped();
|
||||
}
|
||||
// Check whether Tor has already built a circuit
|
||||
info = controlConnection.getInfo("status/circuit-established");
|
||||
if ("1".equals(info)) {
|
||||
LOG.info("Tor has already built a circuit");
|
||||
state.getAndSetCircuitBuilt(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
@@ -316,18 +332,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
bind();
|
||||
}
|
||||
|
||||
// TODO: Remove after a reasonable migration period (added 2020-06-25)
|
||||
private Settings migrateSettings(Settings settings) {
|
||||
int network = settings.getInt(PREF_TOR_NETWORK,
|
||||
DEFAULT_PREF_TOR_NETWORK);
|
||||
if (network == PREF_TOR_NETWORK_NEVER) {
|
||||
settings.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
|
||||
settings.putBoolean(PREF_PLUGIN_ENABLE, false);
|
||||
callback.mergeSettings(settings);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private boolean assetsAreUpToDate() {
|
||||
return doneFile.lastModified() > getLastUpdateTime();
|
||||
}
|
||||
@@ -337,9 +341,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
// The done file may already exist from a previous installation
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
doneFile.delete();
|
||||
// The GeoIP file may exist from a previous installation - we can
|
||||
// save some space by deleting it.
|
||||
// TODO: Remove after a reasonable migration period
|
||||
// (added 2022-03-29)
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
geoIpFile.delete();
|
||||
installTorExecutable();
|
||||
installObfs4Executable();
|
||||
extract(getGeoIpInputStream(), geoIpFile);
|
||||
extract(getConfigInputStream(), configFile);
|
||||
if (!doneFile.createNewFile())
|
||||
LOG.warning("Failed to create done file");
|
||||
@@ -377,14 +386,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return zin;
|
||||
}
|
||||
|
||||
private InputStream getGeoIpInputStream() throws IOException {
|
||||
InputStream in = resourceProvider.getResourceInputStream("geoip",
|
||||
".zip");
|
||||
ZipInputStream zin = new ZipInputStream(in);
|
||||
if (zin.getNextEntry() == null) throw new IOException();
|
||||
return zin;
|
||||
}
|
||||
|
||||
private InputStream getObfs4InputStream() throws IOException {
|
||||
InputStream in = resourceProvider
|
||||
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
|
||||
@@ -542,20 +543,24 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||
}
|
||||
|
||||
private void enableBridges(boolean enable, boolean needsMeek)
|
||||
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
|
||||
throws IOException {
|
||||
if (enable) {
|
||||
Collection<String> conf = new ArrayList<>();
|
||||
conf.add("UseBridges 1");
|
||||
File obfs4File = getObfs4ExecutableFile();
|
||||
if (needsMeek) {
|
||||
if (bridgeTypes.contains(MEEK)) {
|
||||
conf.add("ClientTransportPlugin meek_lite exec " +
|
||||
obfs4File.getAbsolutePath());
|
||||
} else {
|
||||
}
|
||||
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
|
||||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
|
||||
conf.add("ClientTransportPlugin obfs4 exec " +
|
||||
obfs4File.getAbsolutePath());
|
||||
}
|
||||
conf.addAll(circumventionProvider.getBridges(needsMeek));
|
||||
for (BridgeType bridgeType : bridgeTypes) {
|
||||
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
||||
}
|
||||
controlConnection.setConf(conf);
|
||||
} else {
|
||||
controlConnection.setConf("UseBridges", "0");
|
||||
@@ -644,7 +649,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Could not connect to v3 "
|
||||
+ scrubOnion(onion3) + ": " + e.toString());
|
||||
+ scrubOnion(onion3) + ": " + e);
|
||||
}
|
||||
tryToClose(s, LOG, WARNING);
|
||||
return null;
|
||||
@@ -680,8 +685,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
byte[] localSeed = alice ? aliceSeed : bobSeed;
|
||||
byte[] remoteSeed = alice ? bobSeed : aliceSeed;
|
||||
String blob = torRendezvousCrypto.getPrivateKeyBlob(localSeed);
|
||||
String localOnion = torRendezvousCrypto.getOnionAddress(localSeed);
|
||||
String remoteOnion = torRendezvousCrypto.getOnionAddress(remoteSeed);
|
||||
String localOnion = torRendezvousCrypto.getOnion(localSeed);
|
||||
String remoteOnion = torRendezvousCrypto.getOnion(remoteSeed);
|
||||
TransportProperties remoteProperties = new TransportProperties();
|
||||
remoteProperties.put(PROP_ONION_V3, remoteOnion);
|
||||
try {
|
||||
@@ -725,9 +730,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public void circuitStatus(String status, String id, String path) {
|
||||
if (status.equals("BUILT") &&
|
||||
state.getAndSetCircuitBuilt()) {
|
||||
LOG.info("First circuit built");
|
||||
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
||||
// DisableNetwork, set our circuitBuilt flag if not already set
|
||||
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
}
|
||||
@@ -738,9 +744,16 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("OR connection " + status + " " + orName);
|
||||
if (status.equals("CLOSED") || status.equals("FAILED")) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||
|
||||
//noinspection IfCanBeSwitch
|
||||
if (status.equals("LAUNCHED")) state.onOrConnectionLaunched();
|
||||
else if (status.equals("FAILED")) state.onOrConnectionFailed();
|
||||
else if (status.equals("CONNECTED")) state.onOrConnectionConnected();
|
||||
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
|
||||
|
||||
if ((status.equals("FAILED") || status.equals("CLOSED")) &&
|
||||
state.getNumOrConnections() == 0) {
|
||||
// Check whether we've lost connectivity
|
||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||
batteryManager.isCharging());
|
||||
@@ -758,24 +771,81 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void message(String severity, String msg) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
||||
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
|
||||
state.setBootstrapped();
|
||||
backoff.reset();
|
||||
if (msg.startsWith("Switching to guard context")) {
|
||||
state.onSwitchingGuardContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unrecognized(String type, String msg) {
|
||||
if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
String[] words = msg.split(" ");
|
||||
if (words.length > 1 && ONION_V3.matcher(words[1]).matches()) {
|
||||
LOG.info("V3 descriptor uploaded");
|
||||
} else {
|
||||
LOG.info("V2 descriptor uploaded");
|
||||
if (type.equals("STATUS_CLIENT")) {
|
||||
handleClientStatus(removeSeverity(msg));
|
||||
} else if (type.equals("STATUS_GENERAL")) {
|
||||
handleGeneralStatus(removeSeverity(msg));
|
||||
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
||||
String[] parts = msg.split(" ");
|
||||
if (parts.length < 2) {
|
||||
LOG.warning("Failed to parse HS_DESC UPLOADED event");
|
||||
} else if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String removeSeverity(String msg) {
|
||||
return msg.replaceFirst("[^ ]+ ", "");
|
||||
}
|
||||
|
||||
private void handleClientStatus(String msg) {
|
||||
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
|
||||
LOG.info("Bootstrapped");
|
||||
state.setBootstrapped();
|
||||
backoff.reset();
|
||||
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
|
||||
if (!state.getAndSetCircuitBuilt(true)) {
|
||||
LOG.info("Circuit built");
|
||||
backoff.reset();
|
||||
}
|
||||
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
|
||||
if (state.getAndSetCircuitBuilt(false)) {
|
||||
LOG.info("Circuit not built");
|
||||
// TODO: Disable and re-enable network to prompt Tor to rebuild
|
||||
// its guard/bridge connections? This will also close any
|
||||
// established circuits, which might still be functioning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGeneralStatus(String msg) {
|
||||
if (msg.startsWith("CLOCK_JUMPED")) {
|
||||
Long time = parseLongArgument(msg, "TIME");
|
||||
if (time != null && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Clock jumped " + time + " seconds");
|
||||
}
|
||||
} else if (msg.startsWith("CLOCK_SKEW")) {
|
||||
Long skew = parseLongArgument(msg, "SKEW");
|
||||
if (skew != null && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Clock is skewed by " + skew + " seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long parseLongArgument(String msg, String argName) {
|
||||
String[] args = msg.split(" ");
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith(argName + "=")) {
|
||||
try {
|
||||
return Long.parseLong(arg.substring(argName.length() + 1));
|
||||
} catch (NumberFormatException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -844,7 +914,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
int reasonsDisabled = 0;
|
||||
boolean enableNetwork = false, enableBridges = false;
|
||||
boolean useMeek = false, enableConnectionPadding = false;
|
||||
boolean enableConnectionPadding = false;
|
||||
List<BridgeType> bridgeTypes =
|
||||
circumventionProvider.getSuitableBridgeTypes(country);
|
||||
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
@@ -873,14 +945,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
enableNetwork = true;
|
||||
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
||||
(automatic && bridgesWork)) {
|
||||
if (ipv6Only ||
|
||||
circumventionProvider.needsMeek(country)) {
|
||||
LOG.info("Using meek bridges");
|
||||
enableBridges = true;
|
||||
useMeek = true;
|
||||
} else {
|
||||
LOG.info("Using obfs4 bridges");
|
||||
enableBridges = true;
|
||||
if (ipv6Only) bridgeTypes = singletonList(MEEK);
|
||||
enableBridges = true;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Using bridge types " + bridgeTypes);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Not using bridges");
|
||||
@@ -898,7 +966,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
try {
|
||||
if (enableNetwork) {
|
||||
enableBridges(enableBridges, useMeek);
|
||||
enableBridges(enableBridges, bridgeTypes);
|
||||
enableConnectionPadding(enableConnectionPadding);
|
||||
useIpv6(ipv6Only);
|
||||
}
|
||||
@@ -938,6 +1006,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Nullable
|
||||
private ServerSocket serverSocket = null;
|
||||
|
||||
@GuardedBy("this")
|
||||
private int orConnectionsPending = 0, orConnectionsConnected = 0;
|
||||
|
||||
private synchronized void setStarted() {
|
||||
started = true;
|
||||
callback.pluginStateChanged(getState());
|
||||
@@ -961,11 +1032,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
private synchronized boolean getAndSetCircuitBuilt() {
|
||||
boolean firstCircuit = !circuitBuilt;
|
||||
circuitBuilt = true;
|
||||
callback.pluginStateChanged(getState());
|
||||
return firstCircuit;
|
||||
private synchronized boolean getAndSetCircuitBuilt(boolean built) {
|
||||
boolean old = circuitBuilt;
|
||||
circuitBuilt = built;
|
||||
if (built != old) callback.pluginStateChanged(getState());
|
||||
return old;
|
||||
}
|
||||
|
||||
private synchronized void enableNetwork(boolean enable) {
|
||||
@@ -1006,5 +1077,58 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private synchronized int getReasonsDisabled() {
|
||||
return getState() == DISABLED ? reasonsDisabled : 0;
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionLaunched() {
|
||||
orConnectionsPending++;
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionFailed() {
|
||||
orConnectionsPending--;
|
||||
if (orConnectionsPending < 0) {
|
||||
LOG.warning("Count was zero before connection failed");
|
||||
orConnectionsPending = 0;
|
||||
}
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionConnected() {
|
||||
orConnectionsPending--;
|
||||
if (orConnectionsPending < 0) {
|
||||
LOG.warning("Count was zero before connection connected");
|
||||
orConnectionsPending = 0;
|
||||
}
|
||||
orConnectionsConnected++;
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onOrConnectionClosed() {
|
||||
orConnectionsConnected--;
|
||||
if (orConnectionsConnected < 0) {
|
||||
LOG.warning("Count was zero before connection closed");
|
||||
orConnectionsConnected = 0;
|
||||
}
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized void onSwitchingGuardContext() {
|
||||
// Tor doesn't seem to report events for connections belonging to
|
||||
// the old guard context, so we have to reset the counters
|
||||
orConnectionsPending = 0;
|
||||
orConnectionsConnected = 0;
|
||||
logOrConnections();
|
||||
}
|
||||
|
||||
private synchronized int getNumOrConnections() {
|
||||
return orConnectionsPending + orConnectionsConnected;
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private void logOrConnections() {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("OR connections: " + orConnectionsPending
|
||||
+ " pending, " + orConnectionsConnected + " connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ interface TorRendezvousCrypto {
|
||||
|
||||
static final int SEED_BYTES = 32;
|
||||
|
||||
String getOnionAddress(byte[] seed);
|
||||
String getOnion(byte[] seed);
|
||||
|
||||
String getPrivateKeyBlob(byte[] seed);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOnionAddress(byte[] seed) {
|
||||
public String getOnion(byte[] seed) {
|
||||
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
|
||||
return crypto.encodeOnionAddress(spec.getA().toByteArray());
|
||||
return crypto.encodeOnion(spec.getA().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,14 +7,13 @@ import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.net.NetworkInterface.getNetworkInterfaces;
|
||||
import static java.util.Collections.list;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -25,14 +24,11 @@ abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
|
||||
out.writeLong(System.currentTimeMillis());
|
||||
out.writeLong(System.nanoTime());
|
||||
out.writeLong(Runtime.getRuntime().freeMemory());
|
||||
Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
|
||||
if (ifaces != null) {
|
||||
for (NetworkInterface i : list(ifaces)) {
|
||||
for (InetAddress a : list(i.getInetAddresses()))
|
||||
out.write(a.getAddress());
|
||||
byte[] hardware = i.getHardwareAddress();
|
||||
if (hardware != null) out.write(hardware);
|
||||
}
|
||||
for (NetworkInterface i : getNetworkInterfaces()) {
|
||||
for (InetAddress a : list(i.getInetAddresses()))
|
||||
out.write(a.getAddress());
|
||||
byte[] hardware = i.getHardwareAddress();
|
||||
if (hardware != null) out.write(hardware);
|
||||
}
|
||||
for (Entry<String, String> e : System.getenv().entrySet()) {
|
||||
out.writeUTF(e.getKey());
|
||||
|
||||
@@ -215,6 +215,23 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
m.getStreamContext(txn, tag)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamContext getStreamContextOnly(TransportId t, byte[] tag)
|
||||
throws DbException {
|
||||
return withManager(t, m ->
|
||||
db.transactionWithNullableResult(false, txn ->
|
||||
m.getStreamContextOnly(txn, tag)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markTagAsRecognised(TransportId t, byte[] tag)
|
||||
throws DbException {
|
||||
withManager(t, m -> {
|
||||
db.transaction(false, txn -> m.markTagAsRecognised(txn, tag));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
|
||||
@@ -48,4 +48,9 @@ interface TransportKeyManager {
|
||||
StreamContext getStreamContext(Transaction txn, byte[] tag)
|
||||
throws DbException;
|
||||
|
||||
@Nullable
|
||||
StreamContext getStreamContextOnly(Transaction txn, byte[] tag);
|
||||
|
||||
void markTagAsRecognised(Transaction txn, byte[] tag) throws DbException;
|
||||
|
||||
}
|
||||
|
||||
@@ -393,56 +393,82 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
// Look up the incoming keys for the tag
|
||||
TagContext tagCtx = inContexts.remove(new Bytes(tag));
|
||||
if (tagCtx == null) return null;
|
||||
MutableIncomingKeys inKeys = tagCtx.inKeys;
|
||||
// Create a stream context
|
||||
StreamContext ctx = new StreamContext(tagCtx.contactId,
|
||||
tagCtx.pendingContactId, transportId,
|
||||
inKeys.getTagKey(), inKeys.getHeaderKey(),
|
||||
tagCtx.streamNumber, tagCtx.handshakeMode);
|
||||
// Update the reordering window
|
||||
ReorderingWindow window = inKeys.getWindow();
|
||||
Change change = window.setSeen(tagCtx.streamNumber);
|
||||
// Add tags for any stream numbers added to the window
|
||||
for (long streamNumber : change.getAdded()) {
|
||||
byte[] addTag = new byte[TAG_LENGTH];
|
||||
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
|
||||
PROTOCOL_VERSION, streamNumber);
|
||||
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
|
||||
tagCtx.contactId, tagCtx.pendingContactId, inKeys,
|
||||
streamNumber, tagCtx.handshakeMode);
|
||||
inContexts.put(new Bytes(addTag), tagCtx1);
|
||||
}
|
||||
// Remove tags for any stream numbers removed from the window
|
||||
for (long streamNumber : change.getRemoved()) {
|
||||
if (streamNumber == tagCtx.streamNumber) continue;
|
||||
byte[] removeTag = new byte[TAG_LENGTH];
|
||||
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
|
||||
PROTOCOL_VERSION, streamNumber);
|
||||
inContexts.remove(new Bytes(removeTag));
|
||||
}
|
||||
// Write the window back to the DB
|
||||
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
|
||||
inKeys.getTimePeriod(), window.getBase(),
|
||||
window.getBitmap());
|
||||
// If the outgoing keys are inactive, activate them
|
||||
MutableTransportKeySet ks = keys.get(tagCtx.keySetId);
|
||||
MutableOutgoingKeys outKeys =
|
||||
ks.getKeys().getCurrentOutgoingKeys();
|
||||
if (!outKeys.isActive()) {
|
||||
LOG.info("Activating outgoing keys");
|
||||
outKeys.activate();
|
||||
considerReplacingOutgoingKeys(ks);
|
||||
db.setTransportKeysActive(txn, transportId, tagCtx.keySetId);
|
||||
}
|
||||
StreamContext ctx = streamContextFromTag(tag);
|
||||
if (ctx == null) return null;
|
||||
markTagAsRecognised(txn, tag);
|
||||
return ctx;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamContext getStreamContextOnly(Transaction txn, byte[] tag) {
|
||||
lock.lock();
|
||||
try {
|
||||
return streamContextFromTag(tag);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private StreamContext streamContextFromTag(byte[] tag) {
|
||||
// Look up the incoming keys for the tag
|
||||
TagContext tagCtx = inContexts.get(new Bytes(tag));
|
||||
if (tagCtx == null) return null;
|
||||
MutableIncomingKeys inKeys = tagCtx.inKeys;
|
||||
// Create a stream context
|
||||
return new StreamContext(tagCtx.contactId,
|
||||
tagCtx.pendingContactId, transportId,
|
||||
inKeys.getTagKey(), inKeys.getHeaderKey(),
|
||||
tagCtx.streamNumber, tagCtx.handshakeMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markTagAsRecognised(Transaction txn, byte[] tag)
|
||||
throws DbException {
|
||||
TagContext tagCtx = inContexts.remove(new Bytes(tag));
|
||||
if (tagCtx == null) return;
|
||||
MutableIncomingKeys inKeys = tagCtx.inKeys;
|
||||
// Update the reordering window
|
||||
ReorderingWindow window = inKeys.getWindow();
|
||||
Change change = window.setSeen(tagCtx.streamNumber);
|
||||
// Add tags for any stream numbers added to the window
|
||||
for (long streamNumber : change.getAdded()) {
|
||||
byte[] addTag = new byte[TAG_LENGTH];
|
||||
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
|
||||
PROTOCOL_VERSION, streamNumber);
|
||||
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
|
||||
tagCtx.contactId, tagCtx.pendingContactId, inKeys,
|
||||
streamNumber, tagCtx.handshakeMode);
|
||||
inContexts.put(new Bytes(addTag), tagCtx1);
|
||||
}
|
||||
// Remove tags for any stream numbers removed from the window
|
||||
for (long streamNumber : change.getRemoved()) {
|
||||
if (streamNumber == tagCtx.streamNumber) continue;
|
||||
byte[] removeTag = new byte[TAG_LENGTH];
|
||||
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
|
||||
PROTOCOL_VERSION, streamNumber);
|
||||
inContexts.remove(new Bytes(removeTag));
|
||||
}
|
||||
// Write the window back to the DB
|
||||
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
|
||||
inKeys.getTimePeriod(), window.getBase(),
|
||||
window.getBitmap());
|
||||
// If the outgoing keys are inactive, activate them
|
||||
MutableTransportKeySet ks = keys.get(tagCtx.keySetId);
|
||||
MutableOutgoingKeys outKeys =
|
||||
ks.getKeys().getCurrentOutgoingKeys();
|
||||
if (!outKeys.isActive()) {
|
||||
LOG.info("Activating outgoing keys");
|
||||
outKeys.activate();
|
||||
considerReplacingOutgoingKeys(ks);
|
||||
db.setTransportKeysActive(txn, transportId, tagCtx.keySetId);
|
||||
}
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@Wakeful
|
||||
private void updateKeys(Transaction txn) throws DbException {
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
||||
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||
d Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||
d Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
|
||||
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
||||
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
|
||||
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
|
||||
n Bridge obfs4 172.105.22.69:80 CBD17B33192A879433AB37C9E142541BD3459ABD cert=rk5YmpKypLsjlS4tjkYaZNBweYMa5tWQRhZ8Q2WRleNOgrhSceKo59BA8kp6kVfaMPXnSw iat-mode=0
|
||||
n Bridge obfs4 46.128.93.192:7346 5D28B8E1D117B8720D56A8513CF32509DCA1D84F cert=ED6tZP50eF0vno09F5gFvoWTMdcWFEX2FtwXOUYRevjzKg30/y701f61Vycnh6HO9gkaMw iat-mode=0
|
||||
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
|
||||
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
|
||||
n Bridge obfs4 192.3.163.88:57145 DEB62DE9643E5956CA4FA78035B48C9BBABE7F29 cert=RMz2z9uVVrioUhx0A8xUmiftRe2RpcXiqIuDfisdIomcHDf82nzfn83X/ixGUiA4rLCAdw 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
|
||||
v Bridge 5.45.96.40:9001 8723B591712AAA03FB92000370BD356AB4997FA7
|
||||
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
|
||||
v Bridge 152.44.197.85:10507 FF07DF6B4720DA4C50F1A025662D50916D6223F6
|
||||
v Bridge 209.216.78.21:443 C870D381E7264CDB83BAEEBF074804808CCCDB8D
|
||||
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
||||
21
bramble-core/src/test/bash/wait-for-mailbox.sh
Executable file
21
bramble-core/src/test/bash/wait-for-mailbox.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
URL="http://127.0.0.1:8000/status"
|
||||
attempt_counter=0
|
||||
max_attempts=200 # 10min - CI for mailbox currently takes ~5min
|
||||
|
||||
echo "Waiting for mailbox to come online at $URL"
|
||||
|
||||
until [[ "$(curl -s -o /dev/null -w '%{http_code}' $URL)" == "401" ]]; do
|
||||
if [ ${attempt_counter} -eq ${max_attempts} ]; then
|
||||
echo "Timed out waiting for mailbox"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '.'
|
||||
attempt_counter=$((attempt_counter + 1))
|
||||
sleep 3
|
||||
done
|
||||
|
||||
echo "Mailbox started"
|
||||
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageContext;
|
||||
import org.briarproject.bramble.test.ValidatorTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
@@ -38,7 +38,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
|
||||
private final Metadata meta = new Metadata();
|
||||
|
||||
public BdfMessageValidatorTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.client;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
@@ -20,13 +21,15 @@ import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -41,15 +44,22 @@ import java.util.Random;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ClientHelperImplTest extends BrambleMockTestCase {
|
||||
@@ -78,13 +88,35 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
|
||||
private final long timestamp = message.getTimestamp();
|
||||
private final Metadata metadata = new Metadata();
|
||||
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
|
||||
private final String label = StringUtils.getRandomString(5);
|
||||
private final String label = getRandomString(5);
|
||||
private final Author author = getAuthor();
|
||||
|
||||
private final ClientHelper clientHelper = new ClientHelperImpl(db,
|
||||
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
|
||||
metadataEncoder, cryptoComponent, authorFactory);
|
||||
|
||||
private final MailboxPropertiesUpdate validMailboxPropsUpdate;
|
||||
|
||||
public ClientHelperImplTest() {
|
||||
validMailboxPropsUpdate = new MailboxPropertiesUpdate(
|
||||
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd",
|
||||
new MailboxAuthToken(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()));
|
||||
}
|
||||
|
||||
private BdfDictionary getValidMailboxPropsUpdateDict() {
|
||||
BdfDictionary dict = new BdfDictionary();
|
||||
dict.put(PROP_KEY_ONION, validMailboxPropsUpdate.getOnion());
|
||||
dict.put(PROP_KEY_AUTHTOKEN, validMailboxPropsUpdate.getAuthToken()
|
||||
.getBytes());
|
||||
dict.put(PROP_KEY_INBOXID, validMailboxPropsUpdate.getInboxId()
|
||||
.getBytes());
|
||||
dict.put(PROP_KEY_OUTBOXID, validMailboxPropsUpdate.getOutboxId()
|
||||
.getBytes());
|
||||
return dict;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLocalMessage() throws Exception {
|
||||
boolean shared = new Random().nextBoolean();
|
||||
@@ -513,4 +545,95 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(eof));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseEmptyMailboxPropsUpdate() throws Exception {
|
||||
BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||
MailboxPropertiesUpdate parsedProps = clientHelper
|
||||
.parseAndValidateMailboxPropertiesUpdate(emptyPropsDict);
|
||||
assertNull(parsedProps);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseValidMailboxPropsUpdate() throws Exception {
|
||||
MailboxPropertiesUpdate parsedProps = clientHelper
|
||||
.parseAndValidateMailboxPropertiesUpdate(
|
||||
getValidMailboxPropsUpdateDict());
|
||||
assertTrue(mailboxPropertiesUpdateEqual(validMailboxPropsUpdate,
|
||||
parsedProps));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateOnionNotDecodable()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
String badOnion = "!" + propsDict.getString(PROP_KEY_ONION)
|
||||
.substring(1);
|
||||
propsDict.put(PROP_KEY_ONION, badOnion);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateOnionWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
String tooLongOnion = propsDict.getString(PROP_KEY_ONION) + "!";
|
||||
propsDict.put(PROP_KEY_ONION, tooLongOnion);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateInboxIdWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.put(PROP_KEY_INBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateOutboxIdWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.put(PROP_KEY_OUTBOXID, getRandomBytes(UniqueId.LENGTH + 1));
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateAuthTokenWrongLength()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.put(PROP_KEY_AUTHTOKEN, getRandomBytes(UniqueId.LENGTH + 1));
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingOnion() throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_ONION);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingAuthToken()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_AUTHTOKEN);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingInboxId() throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_INBOXID);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMailboxPropsUpdateMissingOutboxId()
|
||||
throws Exception {
|
||||
BdfDictionary propsDict = getValidMailboxPropsUpdateDict();
|
||||
propsDict.remove(PROP_KEY_OUTBOXID);
|
||||
clientHelper.parseAndValidateMailboxPropertiesUpdate(propsDict);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -922,11 +922,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(ids));
|
||||
oneOf(database).getMessage(txn, messageId);
|
||||
will(returnValue(message));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||
maxLatency);
|
||||
oneOf(database).getMessage(txn, messageId1);
|
||||
will(returnValue(message1));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
||||
maxLatency);
|
||||
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
@@ -951,9 +951,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).getMessagesToOffer(txn, contactId, 123, maxLatency);
|
||||
will(returnValue(ids));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||
maxLatency);
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
||||
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
||||
maxLatency);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
@@ -1005,12 +1005,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(ids));
|
||||
oneOf(database).getMessage(txn, messageId);
|
||||
will(returnValue(message));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
||||
maxLatency);
|
||||
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||
messageId, maxLatency);
|
||||
oneOf(database).getMessage(txn, messageId1);
|
||||
will(returnValue(message1));
|
||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
||||
maxLatency);
|
||||
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||
messageId1, maxLatency);
|
||||
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
||||
|
||||
@@ -72,9 +72,6 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
@@ -444,7 +441,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertOneMessageToSendEagerly(db, txn);
|
||||
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should no longer be sendable via lazy retransmission,
|
||||
// but it should still be sendable via eager retransmission
|
||||
@@ -1811,7 +1808,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertFalse(status.isSeen());
|
||||
|
||||
// Pretend the message was sent to the contact
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, Integer.MAX_VALUE);
|
||||
db.updateRetransmissionData(txn, contactId, messageId,
|
||||
Integer.MAX_VALUE);
|
||||
|
||||
// The message should be sent but not seen
|
||||
status = db.getMessageStatus(txn, contactId, messageId);
|
||||
@@ -2052,12 +2050,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Update the message's expiry time as though we sent it - now the
|
||||
// message should be sendable after one round-trip
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, 1000);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Update the message's expiry time again - now it should be sendable
|
||||
// after two round-trips
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, 1000);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Delete the message - there should be no messages to send
|
||||
@@ -2124,7 +2122,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Time: now
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
@@ -2161,6 +2159,51 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||
|
||||
// Retrieve the message from the database
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
|
||||
// Mark the message as sent
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// The message should not be sendable via the same transport
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// The message should be sendable via a transport with a lower latency
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
||||
MAX_LATENCY - 1);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
|
||||
// The message should not be sendable via a slower transport
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
||||
MAX_LATENCY + 1);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetRetransmissionTimes() throws Exception {
|
||||
long now = System.currentTimeMillis();
|
||||
AtomicLong time = new AtomicLong(now);
|
||||
Database<Connection> db =
|
||||
open(false, new TestMessageFactory(), new SettableClock(time));
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addIdentity(txn, identity);
|
||||
assertEquals(contactId,
|
||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||
|
||||
// Time: now
|
||||
// Retrieve the message from the database
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -2169,70 +2212,27 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Time: now
|
||||
// Mark the message as sent
|
||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
|
||||
|
||||
// The message should expire after 2 * MAX_LATENCY
|
||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Time: now
|
||||
// The message should not be sendable via the same transport
|
||||
// Time: now + MAX_LATENCY * 2 - 1
|
||||
// The message should not yet be sendable
|
||||
time.set(now + MAX_LATENCY * 2 - 1);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// Time: now
|
||||
// The message should be sendable via a transport with a faster ETA
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
||||
MAX_LATENCY - 1);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
// Reset the retransmission times
|
||||
db.resetUnackedMessagesToSend(txn, contactId);
|
||||
|
||||
// Time: now + 1
|
||||
// The message should no longer be sendable via the faster transport,
|
||||
// as the ETA is now equal
|
||||
time.set(now + 1);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
||||
MAX_LATENCY - 1);
|
||||
assertTrue(ids.isEmpty());
|
||||
// The message should have infinitely short expiry
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
// The message should be sendable
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertFalse(ids.isEmpty());
|
||||
|
||||
@Test
|
||||
public void testCompactionTime() throws Exception {
|
||||
MessageFactory messageFactory = new TestMessageFactory();
|
||||
long now = System.currentTimeMillis();
|
||||
AtomicLong time = new AtomicLong(now);
|
||||
Clock clock = new SettableClock(time);
|
||||
|
||||
// Time: now
|
||||
// The last compaction time should be initialised to the current time
|
||||
Database<Connection> db = open(false, messageFactory, clock);
|
||||
Connection txn = db.startTransaction();
|
||||
Settings s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
assertEquals(now, s.getLong(LAST_COMPACTED_KEY, 0));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
|
||||
// Time: now + MAX_COMPACTION_INTERVAL_MS
|
||||
// The DB should not be compacted, so the last compaction time should
|
||||
// not be updated
|
||||
time.set(now + MAX_COMPACTION_INTERVAL_MS);
|
||||
db = open(true, messageFactory, clock);
|
||||
txn = db.startTransaction();
|
||||
s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
assertEquals(now, s.getLong(LAST_COMPACTED_KEY, 0));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
|
||||
// Time: now + MAX_COMPACTION_INTERVAL_MS + 1
|
||||
// The DB should be compacted, so the last compaction time should be
|
||||
// updated
|
||||
time.set(now + MAX_COMPACTION_INTERVAL_MS + 1);
|
||||
db = open(true, messageFactory, clock);
|
||||
txn = db.startTransaction();
|
||||
s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
assertEquals(now + MAX_COMPACTION_INTERVAL_MS + 1,
|
||||
s.getLong(LAST_COMPACTED_KEY, 0));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.auto.Mock;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.jmock.integration.junit4.JUnitRuleMockery;
|
||||
import org.jmock.lib.concurrent.Synchroniser;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -35,7 +35,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
@Rule
|
||||
public JUnitRuleMockery context = new JUnitRuleMockery() {{
|
||||
// So we can mock concrete classes like KeyAgreementTransport
|
||||
setImposteriser(ClassImposteriser.INSTANCE);
|
||||
setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||
setThreadingPolicy(new Synchroniser());
|
||||
}};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -58,7 +58,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
private KeyAgreementTransport kat;
|
||||
|
||||
public KeyAgreementTransportTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||
inputStream = context.mock(InputStream.class);
|
||||
outputStream = context.mock(OutputStream.class);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,802 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFileId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import okio.Buffer;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContactId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.readBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.writeBytes;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MailboxApiTest extends BrambleTestCase {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
private final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.socketFactory(SocketFactory.getDefault())
|
||||
.connectTimeout(60_000, MILLISECONDS)
|
||||
.build();
|
||||
private final WeakSingletonProvider<OkHttpClient> httpClientProvider =
|
||||
new WeakSingletonProvider<OkHttpClient>() {
|
||||
@Override
|
||||
@Nonnull
|
||||
public OkHttpClient createInstance() {
|
||||
return client;
|
||||
}
|
||||
};
|
||||
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
|
||||
|
||||
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
||||
private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId());
|
||||
private final ContactId contactId = getContactId();
|
||||
private final MailboxAuthToken contactToken =
|
||||
new MailboxAuthToken(getRandomId());
|
||||
private final MailboxFolderId contactInboxId =
|
||||
new MailboxFolderId(getRandomId());
|
||||
private final MailboxFolderId contactOutboxId =
|
||||
new MailboxFolderId(getRandomId());
|
||||
private final MailboxContact mailboxContact = new MailboxContact(
|
||||
contactId, contactToken, contactInboxId, contactOutboxId);
|
||||
|
||||
@Test
|
||||
public void testSetup() throws Exception {
|
||||
String validResponse = "{\"token\":\"" + token2 + "\"}";
|
||||
String invalidResponse = "{\"foo\":\"bar\"}";
|
||||
String invalidTokenResponse = "{\"token\":{\"foo\":\"bar\"}}";
|
||||
String invalidTokenResponse2 =
|
||||
"{\"token\":\"" + getRandomString(64) + "\"}";
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse().setBody(validResponse));
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.enqueue(new MockResponse().setBody(invalidTokenResponse));
|
||||
server.enqueue(new MockResponse().setBody(invalidTokenResponse2));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
MailboxProperties properties2 =
|
||||
new MailboxProperties(baseUrl, token2, true);
|
||||
|
||||
// valid response with valid token
|
||||
assertEquals(token2, api.setup(properties));
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/setup", request1.getPath());
|
||||
assertEquals("PUT", request1.getMethod());
|
||||
assertToken(request1, token);
|
||||
|
||||
// empty body
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/setup", request2.getPath());
|
||||
assertEquals("PUT", request2.getMethod());
|
||||
assertToken(request2, token);
|
||||
|
||||
// invalid response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/setup", request3.getPath());
|
||||
assertEquals("PUT", request3.getMethod());
|
||||
assertToken(request3, token);
|
||||
|
||||
// 401 response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties2));
|
||||
RecordedRequest request4 = server.takeRequest();
|
||||
assertEquals("/setup", request4.getPath());
|
||||
assertEquals("PUT", request4.getMethod());
|
||||
assertToken(request4, token2);
|
||||
|
||||
// 500 response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request5 = server.takeRequest();
|
||||
assertEquals("/setup", request5.getPath());
|
||||
assertEquals("PUT", request5.getMethod());
|
||||
assertToken(request5, token);
|
||||
|
||||
// invalid json dict token response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request6 = server.takeRequest();
|
||||
assertEquals("/setup", request6.getPath());
|
||||
assertEquals("PUT", request6.getMethod());
|
||||
assertToken(request6, token);
|
||||
|
||||
// invalid non-hex string token response
|
||||
assertThrows(ApiException.class, () -> api.setup(properties));
|
||||
RecordedRequest request7 = server.takeRequest();
|
||||
assertEquals("/setup", request7.getPath());
|
||||
assertEquals("PUT", request7.getMethod());
|
||||
assertToken(request7, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetupOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> api.setup(properties)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatus() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
MailboxProperties properties2 =
|
||||
new MailboxProperties(baseUrl, token2, true);
|
||||
|
||||
assertTrue(api.checkStatus(properties));
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/status", request1.getPath());
|
||||
assertToken(request1, token);
|
||||
|
||||
assertThrows(ApiException.class, () -> api.checkStatus(properties2));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/status", request2.getPath());
|
||||
assertToken(request2, token2);
|
||||
|
||||
assertFalse(api.checkStatus(properties));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/status", request3.getPath());
|
||||
assertToken(request3, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatusOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> api.checkStatus(properties)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWipe() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse().setResponseCode(204));
|
||||
server.enqueue(new MockResponse().setResponseCode(200));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
MailboxProperties properties2 =
|
||||
new MailboxProperties(baseUrl, token2, true);
|
||||
|
||||
api.wipeMailbox(properties);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/", request1.getPath());
|
||||
assertEquals("DELETE", request1.getMethod());
|
||||
assertToken(request1, token);
|
||||
|
||||
assertThrows(ApiException.class, () -> api.wipeMailbox(properties2));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/", request2.getPath());
|
||||
assertEquals("DELETE", request2.getMethod());
|
||||
assertToken(request2, token2);
|
||||
|
||||
assertThrows(ApiException.class, () -> api.wipeMailbox(properties));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/", request3.getPath());
|
||||
assertEquals("DELETE", request3.getMethod());
|
||||
assertToken(request3, token);
|
||||
|
||||
assertThrows(ApiException.class, () -> api.wipeMailbox(properties));
|
||||
RecordedRequest request4 = server.takeRequest();
|
||||
assertEquals("/", request4.getPath());
|
||||
assertEquals("DELETE", request4.getMethod());
|
||||
assertToken(request4, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWipeOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.wipeMailbox(properties));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddContact() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(409));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// contact gets added as expected
|
||||
api.addContact(properties, mailboxContact);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/contacts", request1.getPath());
|
||||
assertToken(request1, token);
|
||||
String expected = "{\"contactId\":" + contactId.getInt() +
|
||||
",\"token\":\"" + contactToken +
|
||||
"\",\"inboxId\":\"" + contactInboxId +
|
||||
"\",\"outboxId\":\"" + contactOutboxId +
|
||||
"\"}";
|
||||
assertEquals(expected, request1.getBody().readUtf8());
|
||||
|
||||
// request is not successful
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.addContact(properties, mailboxContact));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/contacts", request2.getPath());
|
||||
assertToken(request2, token);
|
||||
|
||||
// contact already exists
|
||||
assertThrows(TolerableFailureException.class, () ->
|
||||
api.addContact(properties, mailboxContact));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/contacts", request3.getPath());
|
||||
assertToken(request3, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddContactOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.addContact(properties, mailboxContact));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteContact() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setResponseCode(205));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(404));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// contact gets deleted as expected
|
||||
api.deleteContact(properties, contactId);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("DELETE", request1.getMethod());
|
||||
assertEquals("/contacts/" + contactId.getInt(), request1.getPath());
|
||||
assertToken(request1, token);
|
||||
|
||||
// request is not returning 200
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.deleteContact(properties, contactId));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("DELETE", request2.getMethod());
|
||||
assertEquals("/contacts/" + contactId.getInt(), request2.getPath());
|
||||
assertToken(request2, token);
|
||||
|
||||
// request is not authorized
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.deleteContact(properties, contactId));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("DELETE", request3.getMethod());
|
||||
assertEquals("/contacts/" + contactId.getInt(), request3.getPath());
|
||||
assertToken(request3, token);
|
||||
|
||||
// tolerable 404 not found error
|
||||
assertThrows(TolerableFailureException.class,
|
||||
() -> api.deleteContact(properties, contactId));
|
||||
RecordedRequest request4 = server.takeRequest();
|
||||
assertEquals("/contacts/" + contactId.getInt(), request4.getPath());
|
||||
assertEquals("DELETE", request4.getMethod());
|
||||
assertToken(request4, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteContactOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.deleteContact(properties, contactId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContacts() throws Exception {
|
||||
ContactId contactId2 = getContactId();
|
||||
String validResponse1 = "{\"contacts\": [" + contactId.getInt() + "] }";
|
||||
String validResponse2 = "{\"contacts\": [" + contactId.getInt() + ", " +
|
||||
contactId2.getInt() + "] }";
|
||||
String invalidResponse1 = "{\"foo\":\"bar\"}";
|
||||
String invalidResponse2 = "{\"contacts\":{\"foo\":\"bar\"}}";
|
||||
String invalidResponse3 = "{\"contacts\": [1, 2, \"foo\"] }";
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse().setBody(validResponse1));
|
||||
server.enqueue(new MockResponse().setBody(validResponse2));
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse1));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse2));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse3));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// valid response with two contacts
|
||||
assertEquals(singletonList(contactId), api.getContacts(properties));
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/contacts", request1.getPath());
|
||||
assertEquals("GET", request1.getMethod());
|
||||
assertToken(request1, token);
|
||||
|
||||
// valid response with two contacts
|
||||
List<ContactId> contacts = new ArrayList<>();
|
||||
contacts.add(contactId);
|
||||
contacts.add(contactId2);
|
||||
assertEquals(contacts, api.getContacts(properties));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/contacts", request2.getPath());
|
||||
assertEquals("GET", request2.getMethod());
|
||||
assertToken(request2, token);
|
||||
|
||||
// empty body
|
||||
assertThrows(ApiException.class, () -> api.getContacts(properties));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/contacts", request3.getPath());
|
||||
assertEquals("GET", request3.getMethod());
|
||||
assertToken(request3, token);
|
||||
|
||||
// invalid response: no contacts key
|
||||
assertThrows(ApiException.class, () -> api.getContacts(properties));
|
||||
RecordedRequest request4 = server.takeRequest();
|
||||
assertEquals("/contacts", request4.getPath());
|
||||
assertEquals("GET", request4.getMethod());
|
||||
assertToken(request4, token);
|
||||
|
||||
// invalid response: no list in contacts
|
||||
assertThrows(ApiException.class, () -> api.getContacts(properties));
|
||||
RecordedRequest request5 = server.takeRequest();
|
||||
assertEquals("/contacts", request5.getPath());
|
||||
assertEquals("GET", request5.getMethod());
|
||||
assertToken(request5, token);
|
||||
|
||||
// invalid response: list with non-numbers
|
||||
assertThrows(ApiException.class, () -> api.getContacts(properties));
|
||||
RecordedRequest request6 = server.takeRequest();
|
||||
assertEquals("/contacts", request6.getPath());
|
||||
assertEquals("GET", request6.getMethod());
|
||||
assertToken(request6, token);
|
||||
|
||||
// 401 not authorized
|
||||
assertThrows(ApiException.class, () -> api.getContacts(properties));
|
||||
RecordedRequest request7 = server.takeRequest();
|
||||
assertEquals("/contacts", request7.getPath());
|
||||
assertEquals("GET", request7.getMethod());
|
||||
assertToken(request7, token);
|
||||
|
||||
// 500 internal server error
|
||||
assertThrows(ApiException.class, () -> api.getContacts(properties));
|
||||
RecordedRequest request8 = server.takeRequest();
|
||||
assertEquals("/contacts", request8.getPath());
|
||||
assertEquals("GET", request8.getMethod());
|
||||
assertToken(request8, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContactsOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> api.getContacts(properties)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddFile() throws Exception {
|
||||
File file = folder.newFile();
|
||||
byte[] bytes = getRandomBytes(1337);
|
||||
writeBytes(file, bytes);
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// file gets uploaded as expected
|
||||
api.addFile(properties, contactInboxId, file);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request1.getPath());
|
||||
assertEquals("POST", request1.getMethod());
|
||||
assertToken(request1, token);
|
||||
assertArrayEquals(bytes, request1.getBody().readByteArray());
|
||||
|
||||
// request is not successful
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.addFile(properties, contactInboxId, file));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request2.getPath());
|
||||
assertEquals("POST", request1.getMethod());
|
||||
assertToken(request2, token);
|
||||
|
||||
// server error
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.addFile(properties, contactInboxId, file));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request3.getPath());
|
||||
assertEquals("POST", request1.getMethod());
|
||||
assertToken(request3, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFiles() throws Exception {
|
||||
MailboxFile mailboxFile1 =
|
||||
new MailboxFile(new MailboxFileId(getRandomId()), 1337);
|
||||
MailboxFile mailboxFile2 =
|
||||
new MailboxFile(new MailboxFileId(getRandomId()),
|
||||
System.currentTimeMillis());
|
||||
String fileResponse1 =
|
||||
new ObjectMapper().writeValueAsString(mailboxFile1);
|
||||
String fileResponse2 =
|
||||
new ObjectMapper().writeValueAsString(mailboxFile2);
|
||||
String validResponse1 = "{\"files\": [" + fileResponse1 + "] }";
|
||||
String validResponse2 = "{\"files\": [" + fileResponse1 + ", " +
|
||||
fileResponse2 + "] }";
|
||||
String invalidResponse1 = "{\"files\":\"bar\"}";
|
||||
String invalidResponse2 = "{\"files\":{\"foo\":\"bar\"}}";
|
||||
String invalidResponse3 = "{\"files\": [" + fileResponse1 + ", 1] }";
|
||||
String invalidResponse4 = "{\"contacts\": [ 1, 2 ] }";
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse().setBody(validResponse1));
|
||||
server.enqueue(new MockResponse().setBody(validResponse2));
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse1));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse2));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse3));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse4));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// valid response with one file
|
||||
List<MailboxFile> received1 = api.getFiles(properties, contactInboxId);
|
||||
assertEquals(1, received1.size());
|
||||
assertEquals(mailboxFile1.name, received1.get(0).name);
|
||||
assertEquals(mailboxFile1.time, received1.get(0).time);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request1.getPath());
|
||||
assertEquals("GET", request1.getMethod());
|
||||
assertToken(request1, token);
|
||||
|
||||
// valid response with two files
|
||||
List<MailboxFile> received2 = api.getFiles(properties, contactInboxId);
|
||||
assertEquals(2, received2.size());
|
||||
assertEquals(mailboxFile1.name, received2.get(0).name);
|
||||
assertEquals(mailboxFile1.time, received2.get(0).time);
|
||||
assertEquals(mailboxFile2.name, received2.get(1).name);
|
||||
assertEquals(mailboxFile2.time, received2.get(1).time);
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request1.getPath());
|
||||
assertEquals("GET", request2.getMethod());
|
||||
assertToken(request2, token);
|
||||
|
||||
// empty body
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(properties, contactInboxId));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request3.getPath());
|
||||
assertEquals("GET", request3.getMethod());
|
||||
assertToken(request3, token);
|
||||
|
||||
// invalid response: string instead of list
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(properties, contactInboxId));
|
||||
RecordedRequest request4 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request4.getPath());
|
||||
assertEquals("GET", request4.getMethod());
|
||||
assertToken(request4, token);
|
||||
|
||||
// invalid response: object instead of list
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(properties, contactInboxId));
|
||||
RecordedRequest request5 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request5.getPath());
|
||||
assertEquals("GET", request5.getMethod());
|
||||
assertToken(request5, token);
|
||||
|
||||
// invalid response: list with non-objects
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(properties, contactInboxId));
|
||||
RecordedRequest request6 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request6.getPath());
|
||||
assertEquals("GET", request6.getMethod());
|
||||
assertToken(request6, token);
|
||||
|
||||
// no files key in root object
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(properties, contactInboxId));
|
||||
RecordedRequest request7 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request7.getPath());
|
||||
assertEquals("GET", request7.getMethod());
|
||||
assertToken(request7, token);
|
||||
|
||||
// 401 not authorized
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(properties, contactInboxId));
|
||||
RecordedRequest request8 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request8.getPath());
|
||||
assertEquals("GET", request8.getMethod());
|
||||
assertToken(request8, token);
|
||||
|
||||
// 500 internal server error
|
||||
assertThrows(ApiException.class,
|
||||
() -> api.getFiles(properties, contactInboxId));
|
||||
RecordedRequest request9 = server.takeRequest();
|
||||
assertEquals("/files/" + contactInboxId, request9.getPath());
|
||||
assertEquals("GET", request9.getMethod());
|
||||
assertToken(request9, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFile() throws Exception {
|
||||
MailboxFileId name = new MailboxFileId(getRandomId());
|
||||
File file1 = folder.newFile();
|
||||
File file2 = folder.newFile();
|
||||
File file3 = folder.newFile();
|
||||
byte[] bytes = getRandomBytes(1337);
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse().setBody(new Buffer().write(bytes)));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// file gets downloaded as expected
|
||||
api.getFile(properties, contactOutboxId, name, file1);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/files/" + contactOutboxId + "/" + name,
|
||||
request1.getPath());
|
||||
assertEquals("GET", request1.getMethod());
|
||||
assertToken(request1, token);
|
||||
assertArrayEquals(bytes, readBytes(file1));
|
||||
|
||||
// request is not successful
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFile(properties, contactOutboxId, name, file2));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/files/" + contactOutboxId + "/" + name,
|
||||
request2.getPath());
|
||||
assertEquals("GET", request1.getMethod());
|
||||
assertToken(request2, token);
|
||||
assertEquals(0, readBytes(file2).length);
|
||||
|
||||
// server error
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFile(properties, contactOutboxId, name, file3));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/files/" + contactOutboxId + "/" + name,
|
||||
request3.getPath());
|
||||
assertEquals("GET", request1.getMethod());
|
||||
assertToken(request3, token);
|
||||
assertEquals(0, readBytes(file3).length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteFile() throws Exception {
|
||||
MailboxFileId name = new MailboxFileId(getRandomId());
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setResponseCode(205));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(404));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// file gets deleted as expected
|
||||
api.deleteFile(properties, contactInboxId, name);
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("DELETE", request1.getMethod());
|
||||
assertEquals("/files/" + contactInboxId + "/" + name,
|
||||
request1.getPath());
|
||||
assertToken(request1, token);
|
||||
|
||||
// request is not returning 200
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.deleteFile(properties, contactInboxId, name));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("DELETE", request2.getMethod());
|
||||
assertEquals("/files/" + contactInboxId + "/" + name,
|
||||
request2.getPath());
|
||||
assertToken(request2, token);
|
||||
|
||||
// request is not authorized
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.deleteFile(properties, contactInboxId, name));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("DELETE", request3.getMethod());
|
||||
assertEquals("/files/" + contactInboxId + "/" + name,
|
||||
request3.getPath());
|
||||
assertToken(request3, token);
|
||||
|
||||
// file not found is tolerable
|
||||
assertThrows(TolerableFailureException.class, () ->
|
||||
api.deleteFile(properties, contactInboxId, name));
|
||||
RecordedRequest request4 = server.takeRequest();
|
||||
assertEquals("DELETE", request4.getMethod());
|
||||
assertEquals("/files/" + contactInboxId + "/" + name,
|
||||
request4.getPath());
|
||||
assertToken(request4, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFolders() throws Exception {
|
||||
MailboxFolderId id1 = new MailboxFolderId(getRandomId());
|
||||
MailboxFolderId id2 = new MailboxFolderId(getRandomId());
|
||||
String validResponse1 = "{\"folders\": [ {\"id\": \"" + id1 + "\"} ] }";
|
||||
String validResponse2 = "{\"folders\": [ {\"id\": \"" + id1 + "\"}, " +
|
||||
"{ \"id\": \"" + id2 + "\"} ] }";
|
||||
String invalidResponse1 = "{\"folders\":\"bar\"}";
|
||||
String invalidResponse2 = "{\"folders\":{\"foo\":\"bar\"}}";
|
||||
String invalidResponse3 =
|
||||
"{\"folders\": [ {\"id\": \"" + id1 + "\", 1] }";
|
||||
String invalidResponse4 = "{\"files\": [ 1, 2 ] }";
|
||||
|
||||
MockWebServer server = new MockWebServer();
|
||||
server.enqueue(new MockResponse().setBody(validResponse1));
|
||||
server.enqueue(new MockResponse().setBody(validResponse2));
|
||||
server.enqueue(new MockResponse());
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse1));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse2));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse3));
|
||||
server.enqueue(new MockResponse().setBody(invalidResponse4));
|
||||
server.enqueue(new MockResponse().setResponseCode(401));
|
||||
server.enqueue(new MockResponse().setResponseCode(500));
|
||||
server.start();
|
||||
String baseUrl = getBaseUrl(server);
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(baseUrl, token, true);
|
||||
|
||||
// valid response with one folders
|
||||
assertEquals(singletonList(id1), api.getFolders(properties));
|
||||
RecordedRequest request1 = server.takeRequest();
|
||||
assertEquals("/folders", request1.getPath());
|
||||
assertEquals("GET", request1.getMethod());
|
||||
assertToken(request1, token);
|
||||
|
||||
// valid response with two folders
|
||||
assertEquals(Arrays.asList(id1, id2), api.getFolders(properties));
|
||||
RecordedRequest request2 = server.takeRequest();
|
||||
assertEquals("/folders", request1.getPath());
|
||||
assertEquals("GET", request2.getMethod());
|
||||
assertToken(request2, token);
|
||||
|
||||
// empty body
|
||||
assertThrows(ApiException.class, () -> api.getFolders(properties));
|
||||
RecordedRequest request3 = server.takeRequest();
|
||||
assertEquals("/folders", request3.getPath());
|
||||
assertEquals("GET", request3.getMethod());
|
||||
assertToken(request3, token);
|
||||
|
||||
// invalid response: string instead of list
|
||||
assertThrows(ApiException.class, () -> api.getFolders(properties));
|
||||
RecordedRequest request4 = server.takeRequest();
|
||||
assertEquals("/folders", request4.getPath());
|
||||
assertEquals("GET", request4.getMethod());
|
||||
assertToken(request4, token);
|
||||
|
||||
// invalid response: object instead of list
|
||||
assertThrows(ApiException.class, () -> api.getFolders(properties));
|
||||
RecordedRequest request5 = server.takeRequest();
|
||||
assertEquals("/folders", request5.getPath());
|
||||
assertEquals("GET", request5.getMethod());
|
||||
assertToken(request5, token);
|
||||
|
||||
// invalid response: list with non-objects
|
||||
assertThrows(ApiException.class, () -> api.getFolders(properties));
|
||||
RecordedRequest request6 = server.takeRequest();
|
||||
assertEquals("/folders", request6.getPath());
|
||||
assertEquals("GET", request6.getMethod());
|
||||
assertToken(request6, token);
|
||||
|
||||
// no folders key in root object
|
||||
assertThrows(ApiException.class, () -> api.getFolders(properties));
|
||||
RecordedRequest request7 = server.takeRequest();
|
||||
assertEquals("/folders", request7.getPath());
|
||||
assertEquals("GET", request7.getMethod());
|
||||
assertToken(request7, token);
|
||||
|
||||
// 401 not authorized
|
||||
assertThrows(ApiException.class, () -> api.getFolders(properties));
|
||||
RecordedRequest request8 = server.takeRequest();
|
||||
assertEquals("/folders", request8.getPath());
|
||||
assertEquals("GET", request8.getMethod());
|
||||
assertToken(request8, token);
|
||||
|
||||
// 500 internal server error
|
||||
assertThrows(ApiException.class, () -> api.getFolders(properties));
|
||||
RecordedRequest request9 = server.takeRequest();
|
||||
assertEquals("/folders", request9.getPath());
|
||||
assertEquals("GET", request9.getMethod());
|
||||
assertToken(request9, token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFoldersOnlyForOwner() {
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties("", token, false);
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
api.getFolders(properties));
|
||||
}
|
||||
|
||||
private String getBaseUrl(MockWebServer server) {
|
||||
String baseUrl = server.url("").toString();
|
||||
return baseUrl.substring(0, baseUrl.length() - 1);
|
||||
}
|
||||
|
||||
private void assertToken(RecordedRequest request, MailboxId token) {
|
||||
assertNotNull(request.getHeader("Authorization"));
|
||||
assertEquals("Bearer " + token, request.getHeader("Authorization"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
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.ApiException;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||
import static org.briarproject.bramble.test.TestUtils.readBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.writeBytes;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
public class MailboxIntegrationTest extends BrambleTestCase {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
private final static String URL_BASE = "http://127.0.0.1:8000";
|
||||
private final static MailboxAuthToken SETUP_TOKEN;
|
||||
|
||||
static {
|
||||
try {
|
||||
SETUP_TOKEN = MailboxAuthToken.fromString(
|
||||
"54686973206973206120736574757020746f6b656e20666f722042726961722e");
|
||||
} catch (InvalidMailboxIdException e) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private static final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.socketFactory(SocketFactory.getDefault())
|
||||
.connectTimeout(60_000, MILLISECONDS)
|
||||
.build();
|
||||
private static final WeakSingletonProvider<OkHttpClient>
|
||||
httpClientProvider =
|
||||
new WeakSingletonProvider<OkHttpClient>() {
|
||||
@Override
|
||||
@Nonnull
|
||||
public OkHttpClient createInstance() {
|
||||
return client;
|
||||
}
|
||||
};
|
||||
private final static MailboxApiImpl api =
|
||||
new MailboxApiImpl(httpClientProvider);
|
||||
// needs to be static to keep values across different tests
|
||||
private static MailboxProperties ownerProperties;
|
||||
|
||||
/**
|
||||
* Called before each test to make sure the mailbox is setup once
|
||||
* before starting with individual tests.
|
||||
* {@link BeforeClass} needs to be static, so we can't use the API class.
|
||||
*/
|
||||
@Before
|
||||
public void ensureSetup() throws IOException, ApiException {
|
||||
// Skip this test unless it's explicitly enabled in the environment
|
||||
assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class));
|
||||
|
||||
if (ownerProperties != null) return;
|
||||
MailboxProperties setupProperties =
|
||||
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
|
||||
MailboxAuthToken ownerToken = api.setup(setupProperties);
|
||||
ownerProperties = new MailboxProperties(URL_BASE, ownerToken, true);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
// we can't test wiping as a regular test as it stops the mailbox
|
||||
public static void wipe() throws IOException, ApiException {
|
||||
if (!isOptionalTestEnabled(MailboxIntegrationTest.class)) return;
|
||||
|
||||
api.wipeMailbox(ownerProperties);
|
||||
|
||||
// check doesn't work anymore
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.checkStatus(ownerProperties));
|
||||
|
||||
// new setup doesn't work as mailbox is stopping
|
||||
MailboxProperties setupProperties =
|
||||
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
|
||||
assertThrows(ApiException.class, () -> api.setup(setupProperties));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatus() throws Exception {
|
||||
assertTrue(api.checkStatus(ownerProperties));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContactApi() throws Exception {
|
||||
ContactId contactId1 = new ContactId(1);
|
||||
ContactId contactId2 = new ContactId(2);
|
||||
MailboxContact mailboxContact1 = getMailboxContact(contactId1);
|
||||
MailboxContact mailboxContact2 = getMailboxContact(contactId2);
|
||||
|
||||
// no contacts initially
|
||||
assertEquals(emptyList(), api.getContacts(ownerProperties));
|
||||
// added contact gets returned
|
||||
api.addContact(ownerProperties, mailboxContact1);
|
||||
assertEquals(singletonList(contactId1),
|
||||
api.getContacts(ownerProperties));
|
||||
// second contact also gets returned
|
||||
api.addContact(ownerProperties, mailboxContact2);
|
||||
assertEquals(Arrays.asList(contactId1, contactId2),
|
||||
api.getContacts(ownerProperties));
|
||||
|
||||
// after both contacts get deleted, the list is empty again
|
||||
api.deleteContact(ownerProperties, contactId1);
|
||||
api.deleteContact(ownerProperties, contactId2);
|
||||
assertEquals(emptyList(), api.getContacts(ownerProperties));
|
||||
|
||||
// deleting again is tolerable
|
||||
assertThrows(TolerableFailureException.class,
|
||||
() -> api.deleteContact(ownerProperties, contactId2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileManagementApi() throws Exception {
|
||||
// add contact, so we can leave each other files
|
||||
ContactId contactId = new ContactId(1);
|
||||
MailboxContact contact = getMailboxContact(contactId);
|
||||
MailboxProperties contactProperties = new MailboxProperties(
|
||||
ownerProperties.getBaseUrl(), contact.token, false);
|
||||
api.addContact(ownerProperties, contact);
|
||||
|
||||
// upload a file for our contact
|
||||
File file1 = folder.newFile();
|
||||
byte[] bytes1 = getRandomBytes(2048);
|
||||
writeBytes(file1, bytes1);
|
||||
api.addFile(ownerProperties, contact.inboxId, file1);
|
||||
|
||||
// contact checks files
|
||||
List<MailboxFile> files1 =
|
||||
api.getFiles(contactProperties, contact.inboxId);
|
||||
assertEquals(1, files1.size());
|
||||
MailboxFileId fileName1 = files1.get(0).name;
|
||||
|
||||
// owner can't check files
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(ownerProperties, contact.inboxId));
|
||||
|
||||
// contact downloads file
|
||||
File file1downloaded = folder.newFile();
|
||||
api.getFile(contactProperties, contact.inboxId, fileName1,
|
||||
file1downloaded);
|
||||
assertArrayEquals(bytes1, readBytes(file1downloaded));
|
||||
|
||||
// owner can't download file, even if knowing name
|
||||
File file1forbidden = folder.newFile();
|
||||
assertThrows(ApiException.class, () -> api.getFile(ownerProperties,
|
||||
contact.inboxId, fileName1, file1forbidden));
|
||||
assertEquals(0, file1forbidden.length());
|
||||
|
||||
// owner can't delete file
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.deleteFile(ownerProperties, contact.inboxId, fileName1));
|
||||
|
||||
// contact deletes file
|
||||
api.deleteFile(contactProperties, contact.inboxId, fileName1);
|
||||
assertEquals(0,
|
||||
api.getFiles(contactProperties, contact.inboxId).size());
|
||||
|
||||
// contact uploads two files for the owner
|
||||
File file2 = folder.newFile();
|
||||
File file3 = folder.newFile();
|
||||
byte[] bytes2 = getRandomBytes(2048);
|
||||
byte[] bytes3 = getRandomBytes(1024);
|
||||
writeBytes(file2, bytes2);
|
||||
writeBytes(file3, bytes3);
|
||||
api.addFile(contactProperties, contact.outboxId, file2);
|
||||
api.addFile(contactProperties, contact.outboxId, file3);
|
||||
|
||||
// owner checks folders with available files
|
||||
List<MailboxFolderId> folders = api.getFolders(ownerProperties);
|
||||
assertEquals(singletonList(contact.outboxId), folders);
|
||||
|
||||
// owner lists files in contact's outbox
|
||||
List<MailboxFile> files2 =
|
||||
api.getFiles(ownerProperties, contact.outboxId);
|
||||
assertEquals(2, files2.size());
|
||||
MailboxFileId file2name = files2.get(0).name;
|
||||
MailboxFileId file3name = files2.get(1).name;
|
||||
|
||||
// contact can't list files in contact's outbox
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.getFiles(contactProperties, contact.outboxId));
|
||||
|
||||
// owner downloads both files from contact's outbox
|
||||
File file2downloaded = folder.newFile();
|
||||
File file3downloaded = folder.newFile();
|
||||
api.getFile(ownerProperties, contact.outboxId, file2name,
|
||||
file2downloaded);
|
||||
api.getFile(ownerProperties, contact.outboxId, file3name,
|
||||
file3downloaded);
|
||||
byte[] downloadedBytes2 = readBytes(file2downloaded);
|
||||
byte[] downloadedBytes3 = readBytes(file3downloaded);
|
||||
// file order is preserved (sorted by time),
|
||||
// so we know what file is which
|
||||
assertArrayEquals(bytes2, downloadedBytes2);
|
||||
assertArrayEquals(bytes3, downloadedBytes3);
|
||||
|
||||
// contact can't download files again, even if knowing name
|
||||
File file2forbidden = folder.newFile();
|
||||
File file3forbidden = folder.newFile();
|
||||
assertThrows(ApiException.class, () -> api.getFile(contactProperties,
|
||||
contact.outboxId, file2name, file2forbidden));
|
||||
assertThrows(ApiException.class, () -> api.getFile(contactProperties,
|
||||
contact.outboxId, file3name, file3forbidden));
|
||||
assertEquals(0, file1forbidden.length());
|
||||
assertEquals(0, file2forbidden.length());
|
||||
|
||||
// contact can't delete files in outbox
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.deleteFile(contactProperties, contact.outboxId, file2name));
|
||||
assertThrows(ApiException.class, () ->
|
||||
api.deleteFile(contactProperties, contact.outboxId, file3name));
|
||||
|
||||
// owner deletes files
|
||||
api.deleteFile(ownerProperties, contact.outboxId, file2name);
|
||||
api.deleteFile(ownerProperties, contact.outboxId, file3name);
|
||||
assertEquals(emptyList(),
|
||||
api.getFiles(ownerProperties, contact.outboxId));
|
||||
assertEquals(emptyList(), api.getFolders(ownerProperties));
|
||||
|
||||
// deleting a non-existent file is tolerable
|
||||
assertThrows(TolerableFailureException.class, () ->
|
||||
api.deleteFile(ownerProperties, contact.outboxId, file3name));
|
||||
|
||||
// owner deletes contact again to leave clean state for other tests
|
||||
api.deleteContact(ownerProperties, contactId);
|
||||
assertEquals(emptyList(), api.getContacts(ownerProperties));
|
||||
}
|
||||
|
||||
private MailboxContact getMailboxContact(ContactId contactId) {
|
||||
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
|
||||
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
|
||||
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
|
||||
return new MailboxContact(contactId, authToken, inboxId, outboxId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.PredicateMatcher;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor executor = new ImmediateExecutor();
|
||||
private final DatabaseComponent db =
|
||||
context.mock(DatabaseComponent.class);
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final MailboxApi api = context.mock(MailboxApi.class);
|
||||
private final MailboxSettingsManager mailboxSettingsManager =
|
||||
context.mock(MailboxSettingsManager.class);
|
||||
private final MailboxPropertyManager mailboxPropertyManager =
|
||||
context.mock(MailboxPropertyManager.class);
|
||||
private final MailboxPairingTaskFactory factory =
|
||||
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api,
|
||||
mailboxSettingsManager, mailboxPropertyManager);
|
||||
|
||||
private final String onion = getRandomString(56);
|
||||
private final byte[] onionBytes = getRandomBytes(32);
|
||||
private final String onionAddress = "http://" + onion + ".onion";
|
||||
private final MailboxAuthToken setupToken =
|
||||
new MailboxAuthToken(getRandomId());
|
||||
private final MailboxAuthToken ownerToken =
|
||||
new MailboxAuthToken(getRandomId());
|
||||
private final String validPayload = getValidPayload();
|
||||
private final long time = System.currentTimeMillis();
|
||||
private final MailboxProperties setupProperties =
|
||||
new MailboxProperties(onionAddress, setupToken, true);
|
||||
private final MailboxProperties ownerProperties =
|
||||
new MailboxProperties(onionAddress, ownerToken, true);
|
||||
|
||||
@Test
|
||||
public void testInitialQrCodeReceivedState() {
|
||||
MailboxPairingTask task =
|
||||
factory.createPairingTask(getRandomString(42));
|
||||
task.addObserver(state ->
|
||||
assertTrue(state instanceof MailboxPairingState.QrCodeReceived)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidQrCode() {
|
||||
MailboxPairingTask task1 =
|
||||
factory.createPairingTask(getRandomString(42));
|
||||
task1.run();
|
||||
task1.addObserver(state ->
|
||||
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
|
||||
);
|
||||
|
||||
String goodLength = "00" + getRandomString(63);
|
||||
MailboxPairingTask task2 = factory.createPairingTask(goodLength);
|
||||
task2.run();
|
||||
task2.addObserver(state ->
|
||||
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulPairing() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).encodeOnion(onionBytes);
|
||||
will(returnValue(onion));
|
||||
oneOf(api).setup(with(matches(setupProperties)));
|
||||
will(returnValue(ownerToken));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
}});
|
||||
Contact contact1 = getContact();
|
||||
Transaction txn = new Transaction(null, false);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
|
||||
with(txn), with(matches(ownerProperties)));
|
||||
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(Collections.singletonList(contact1)));
|
||||
oneOf(mailboxPropertyManager).getRemoteProperties(txn,
|
||||
contact1.getId());
|
||||
will(returnValue(null));
|
||||
oneOf(db).resetUnackedMessagesToSend(txn, contact1.getId());
|
||||
}});
|
||||
|
||||
AtomicInteger i = new AtomicInteger(0);
|
||||
MailboxPairingTask task = factory.createPairingTask(validPayload);
|
||||
task.addObserver(state -> {
|
||||
if (i.get() == 0) {
|
||||
assertEquals(MailboxPairingState.QrCodeReceived.class,
|
||||
state.getClass());
|
||||
} else if (i.get() == 1) {
|
||||
assertEquals(MailboxPairingState.Pairing.class,
|
||||
state.getClass());
|
||||
} else if (i.get() == 2) {
|
||||
assertEquals(MailboxPairingState.Paired.class,
|
||||
state.getClass());
|
||||
} else fail("Unexpected change of state " + state.getClass());
|
||||
i.getAndIncrement();
|
||||
});
|
||||
task.run();
|
||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlreadyPaired() throws Exception {
|
||||
testApiException(new MailboxApi.MailboxAlreadyPairedException(),
|
||||
MailboxPairingState.MailboxAlreadyPaired.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMailboxApiException() throws Exception {
|
||||
testApiException(new MailboxApi.ApiException(),
|
||||
MailboxPairingState.UnexpectedError.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiIOException() throws Exception {
|
||||
testApiException(new IOException(),
|
||||
MailboxPairingState.ConnectionError.class);
|
||||
}
|
||||
|
||||
private void testApiException(Exception e,
|
||||
Class<? extends MailboxPairingState> s) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).encodeOnion(onionBytes);
|
||||
will(returnValue(onion));
|
||||
oneOf(api).setup(with(matches(setupProperties)));
|
||||
will(throwException(e));
|
||||
}});
|
||||
|
||||
MailboxPairingTask task = factory.createPairingTask(validPayload);
|
||||
task.run();
|
||||
task.addObserver(state -> assertEquals(state.getClass(), s));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDbException() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).encodeOnion(onionBytes);
|
||||
will(returnValue(onion));
|
||||
oneOf(api).setup(with(matches(setupProperties)));
|
||||
will(returnValue(ownerToken));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
}});
|
||||
Transaction txn = new Transaction(null, false);
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
|
||||
with(txn), with(matches(ownerProperties)));
|
||||
will(throwException(new DbException()));
|
||||
}});
|
||||
|
||||
MailboxPairingTask task = factory.createPairingTask(validPayload);
|
||||
task.run();
|
||||
task.addObserver(state -> assertEquals(state.getClass(),
|
||||
MailboxPairingState.UnexpectedError.class));
|
||||
}
|
||||
|
||||
private String getValidPayload() {
|
||||
byte[] payloadBytes = ByteBuffer.allocate(65)
|
||||
.put((byte) 32) // 1
|
||||
.put(onionBytes) // 32
|
||||
.put(setupToken.getBytes()) // 32
|
||||
.array();
|
||||
//noinspection CharsetObjectCanBeUsed
|
||||
return new String(payloadBytes, Charset.forName("ISO-8859-1"));
|
||||
}
|
||||
|
||||
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
|
||||
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
|
||||
p1.getAuthToken().equals(p2.getAuthToken()) &&
|
||||
p1.getBaseUrl().equals(p2.getBaseUrl()) &&
|
||||
p1.isOwner() == p2.isOwner());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||
import org.briarproject.bramble.api.mailbox.RemoteMailboxPropertiesUpdateEvent;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MAJOR_VERSION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.MSG_KEY_VERSION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_AUTHTOKEN;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_INBOXID;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_ONION;
|
||||
import static org.briarproject.bramble.api.mailbox.MailboxPropertyManager.PROP_KEY_OUTBOXID;
|
||||
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.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.briarproject.bramble.test.TestUtils.mailboxPropertiesUpdateEqual;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MailboxPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final ClientVersioningManager clientVersioningManager =
|
||||
context.mock(ClientVersioningManager.class);
|
||||
private final MetadataParser metadataParser =
|
||||
context.mock(MetadataParser.class);
|
||||
private final ContactGroupFactory contactGroupFactory =
|
||||
context.mock(ContactGroupFactory.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||
private final MailboxSettingsManager mailboxSettingsManager =
|
||||
context.mock(MailboxSettingsManager.class);
|
||||
|
||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
private final BdfDictionary propsDict;
|
||||
private final BdfDictionary emptyPropsDict = new BdfDictionary();
|
||||
private final MailboxPropertiesUpdate props;
|
||||
private final MailboxProperties ownProps;
|
||||
|
||||
public MailboxPropertyManagerImplTest() {
|
||||
ownProps = new MailboxProperties("http://bar.onion",
|
||||
new MailboxAuthToken(getRandomId()), true);
|
||||
props = new MailboxPropertiesUpdate(ownProps.getOnion(),
|
||||
new MailboxAuthToken(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()));
|
||||
propsDict = new BdfDictionary();
|
||||
propsDict.put(PROP_KEY_ONION, props.getOnion());
|
||||
propsDict.put(PROP_KEY_AUTHTOKEN, props.getAuthToken().getBytes());
|
||||
propsDict.put(PROP_KEY_INBOXID, props.getInboxId().getBytes());
|
||||
propsDict.put(PROP_KEY_OUTBOXID, props.getOutboxId().getBytes());
|
||||
}
|
||||
|
||||
private MailboxPropertyManagerImpl createInstance() {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
|
||||
MAJOR_VERSION);
|
||||
will(returnValue(localGroup));
|
||||
}});
|
||||
return new MailboxPropertyManagerImpl(db, clientHelper,
|
||||
clientVersioningManager, metadataParser, contactGroupFactory,
|
||||
clock, mailboxSettingsManager, crypto);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesGroupsAtUnpairedStartup() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.onDatabaseOpened(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesGroupsAndCreatesAndSendsAtPairedStartup()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(ownProps));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getAuthToken()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getInboxId()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getOutboxId()));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.onDatabaseOpened(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(true));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.onDatabaseOpened(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesContactGroupWhenAddingContactUnpaired()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Create the group and share it with the contact
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.addingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesContactGroupAndCreatesAndSendsWhenAddingContactPaired()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Create the group and share it with the contact
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(clientVersioningManager).getClientVisibility(txn,
|
||||
contact.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||
will(returnValue(SHARED));
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
|
||||
will(returnValue(ownProps));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getAuthToken()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getInboxId()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getOutboxId()));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 1, true);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.addingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).removeGroup(txn, contactGroup);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.removingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotDeleteAnythingWhenFirstUpdateIsDelivered()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
GroupId contactGroupId = new GroupId(getRandomId());
|
||||
Message message = getMessage(contactGroupId);
|
||||
BdfList body = BdfList.of(1, propsDict);
|
||||
Metadata meta = new Metadata();
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
// A local update should be ignored
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(localUpdateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(metadataParser).parse(meta);
|
||||
will(returnValue(metaDictionary));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getContactId(txn, contactGroupId);
|
||||
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||
will(returnValue(body));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertTrue(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesOlderUpdateWhenUpdateIsDelivered()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
GroupId contactGroupId = new GroupId(getRandomId());
|
||||
Message message = getMessage(contactGroupId);
|
||||
BdfList body = BdfList.of(1, propsDict);
|
||||
Metadata meta = new Metadata();
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 2),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
// This older version should be deleted
|
||||
MessageId updateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(updateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
// A local update should be ignored
|
||||
MessageId localUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(localUpdateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(metadataParser).parse(meta);
|
||||
will(returnValue(metaDictionary));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(db).deleteMessage(txn, updateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, updateId);
|
||||
oneOf(clientHelper).getContactId(txn, contactGroupId);
|
||||
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||
will(returnValue(body));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertTrue(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeletesObsoleteUpdateWhenDelivered() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
GroupId contactGroupId = new GroupId(getRandomId());
|
||||
Message message = getMessage(contactGroupId);
|
||||
Metadata meta = new Metadata();
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 3),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
// This newer version should not be deleted
|
||||
MessageId updateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(updateId, BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 4),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(metadataParser).parse(meta);
|
||||
will(returnValue(metaDictionary));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(db).deleteMessage(txn, message.getId());
|
||||
oneOf(db).deleteMessageMetadata(txn, message.getId());
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertEquals(ACCEPT_DO_NOT_SHARE,
|
||||
t.incomingMessage(txn, message, meta));
|
||||
assertFalse(hasEvent(txn, RemoteMailboxPropertiesUpdateEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesAndStoresLocalPropertiesWithNewVersionOnPairing()
|
||||
throws Exception {
|
||||
Contact contact = getContact();
|
||||
List<Contact> contacts = singletonList(contact);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
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)
|
||||
));
|
||||
// Some remote props, ignored
|
||||
messageMetadata.put(new MessageId(getRandomId()), BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 3),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getAuthToken()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getInboxId()));
|
||||
oneOf(crypto).generateUniqueId();
|
||||
will(returnValue(props.getOutboxId()));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), propsDict, 2, true);
|
||||
oneOf(db).removeMessage(txn, latestId);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.mailboxPaired(txn, ownProps.getOnion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoresEmptyLocalPropertiesWithNewVersionOnUnpairing()
|
||||
throws Exception {
|
||||
Contact contact = getContact();
|
||||
List<Contact> contacts = singletonList(contact);
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
|
||||
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)
|
||||
));
|
||||
// Some remote props, ignored
|
||||
messageMetadata.put(new MessageId(getRandomId()), BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 3),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
expectStoreMessage(txn, contactGroup.getId(), emptyPropsDict,
|
||||
2, true);
|
||||
oneOf(db).removeMessage(txn, latestId);
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
t.mailboxUnpaired(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRemoteProperties()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, propsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
MailboxPropertiesUpdate remote =
|
||||
t.getRemoteProperties(txn, contact.getId());
|
||||
assertTrue(mailboxPropertiesUpdateEqual(remote, props));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRemotePropertiesReturnsNullBecauseNoUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(emptyMessageMetadata));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getRemoteProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRemotePropertiesReturnsNullBecauseEmptyUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, false)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, emptyPropsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
emptyPropsDict);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getRemoteProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalProperties()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, propsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
propsDict);
|
||||
will(returnValue(props));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
MailboxPropertiesUpdate local =
|
||||
t.getLocalProperties(txn, contact.getId());
|
||||
assertTrue(mailboxPropertiesUpdateEqual(local, props));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalPropertiesReturnsNullBecauseNoUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
Map<MessageId, BdfDictionary> emptyMessageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(emptyMessageMetadata));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getLocalProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalPropertiesReturnsNullBecauseEmptyUpdate()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact();
|
||||
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, 1),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true)
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
MessageId fooUpdateId = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooUpdateId, metaDictionary);
|
||||
BdfList fooUpdate = BdfList.of(1, emptyPropsDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
MAJOR_VERSION, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
emptyPropsDict);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
MailboxPropertyManagerImpl t = createInstance();
|
||||
assertNull(t.getLocalProperties(txn, contact.getId()));
|
||||
}
|
||||
|
||||
private void expectStoreMessage(Transaction txn, GroupId g,
|
||||
BdfDictionary properties, long version, boolean local)
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(version, properties);
|
||||
Message message = getMessage(g);
|
||||
long timestamp = message.getTimestamp();
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_VERSION, version),
|
||||
new BdfEntry(MSG_KEY_LOCAL, local)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(timestamp));
|
||||
oneOf(clientHelper).createMessage(g, timestamp, body);
|
||||
will(returnValue(message));
|
||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, true,
|
||||
false);
|
||||
}});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertiesUpdate;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MailboxPropertyValidatorTest extends BrambleMockTestCase {
|
||||
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
|
||||
private final BdfDictionary bdfDict;
|
||||
private final MailboxPropertiesUpdate mailboxProps;
|
||||
private final Group group;
|
||||
private final Message message;
|
||||
private final MailboxPropertyValidator mpv;
|
||||
|
||||
public MailboxPropertyValidatorTest() {
|
||||
// Just dummies, clientHelper is mocked so our test is a bit shallow;
|
||||
// not testing
|
||||
// {@link ClientHelper#parseAndValidateMailboxPropertiesUpdate(BdfDictionary)}
|
||||
bdfDict = BdfDictionary.of(new BdfEntry("foo", "bar"));
|
||||
mailboxProps = new MailboxPropertiesUpdate("baz",
|
||||
new MailboxAuthToken(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()),
|
||||
new MailboxFolderId(getRandomId()));
|
||||
|
||||
group = getGroup(MailboxPropertyManager.CLIENT_ID,
|
||||
MailboxPropertyManager.MAJOR_VERSION);
|
||||
message = getMessage(group.getId());
|
||||
|
||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
mpv = new MailboxPropertyValidator(clientHelper, metadataEncoder,
|
||||
clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateMessageBody() throws IOException {
|
||||
BdfList body = BdfList.of(4, bdfDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
bdfDict);
|
||||
will(returnValue(mailboxProps));
|
||||
}});
|
||||
|
||||
BdfDictionary result =
|
||||
mpv.validateMessage(message, group, body).getDictionary();
|
||||
assertEquals(4, result.getLong("version").longValue());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateWrongVersionValue() throws IOException {
|
||||
BdfList body = BdfList.of(-1, bdfDict);
|
||||
mpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateWrongVersionType() throws IOException {
|
||||
BdfList body = BdfList.of(bdfDict, bdfDict);
|
||||
mpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyPropertiesReturnsNull() throws IOException {
|
||||
BdfDictionary emptyBdfDict = new BdfDictionary();
|
||||
BdfList body = BdfList.of(42, emptyBdfDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||
emptyBdfDict);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
BdfDictionary result =
|
||||
mpv.validateMessage(message, group, body).getDictionary();
|
||||
assertEquals(42, result.getLong("version").longValue());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,29 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
|
||||
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.OwnMailboxConnectionStatusEvent;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
|
||||
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
|
||||
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_UPLOADS_NAMESPACE;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -29,8 +37,12 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final MailboxSettingsManager manager =
|
||||
new MailboxSettingsManagerImpl(settingsManager);
|
||||
private final Random random = new Random();
|
||||
private final String onion = getRandomString(64);
|
||||
private final String token = getRandomString(64);
|
||||
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
|
||||
private final ContactId contactId1 = new ContactId(random.nextInt());
|
||||
private final ContactId contactId2 = new ContactId(random.nextInt());
|
||||
private final ContactId contactId3 = new ContactId(random.nextInt());
|
||||
private final long now = System.currentTimeMillis();
|
||||
private final long lastAttempt = now - 1234;
|
||||
private final long lastSuccess = now - 2345;
|
||||
@@ -54,7 +66,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Settings settings = new Settings();
|
||||
settings.put(SETTINGS_KEY_ONION, onion);
|
||||
settings.put(SETTINGS_KEY_TOKEN, token);
|
||||
settings.put(SETTINGS_KEY_TOKEN, token.toString());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||
@@ -63,7 +75,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
|
||||
assertNotNull(properties);
|
||||
assertEquals(onion, properties.getOnionAddress());
|
||||
assertEquals(onion, properties.getBaseUrl());
|
||||
assertEquals(token, properties.getAuthToken());
|
||||
assertTrue(properties.isOwner());
|
||||
}
|
||||
@@ -73,7 +85,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Settings expectedSettings = new Settings();
|
||||
expectedSettings.put(SETTINGS_KEY_ONION, onion);
|
||||
expectedSettings.put(SETTINGS_KEY_TOKEN, token);
|
||||
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
|
||||
MailboxProperties properties =
|
||||
new MailboxProperties(onion, token, true);
|
||||
|
||||
@@ -134,6 +146,7 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
manager.recordSuccessfulConnection(txn, now);
|
||||
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -166,4 +179,53 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
manager.recordFailedConnectionAttempt(txn, now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGettingPendingUploads() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Settings settings = new Settings();
|
||||
settings.put(String.valueOf(contactId1.getInt()), onion);
|
||||
settings.put(String.valueOf(contactId2.getInt()), token.toString());
|
||||
settings.put(String.valueOf(contactId3.getInt()), "");
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
exactly(4).of(settingsManager)
|
||||
.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
|
||||
will(returnValue(settings));
|
||||
}});
|
||||
|
||||
String filename1 = manager.getPendingUpload(txn, contactId1);
|
||||
assertEquals(onion, filename1);
|
||||
String filename2 = manager.getPendingUpload(txn, contactId2);
|
||||
assertNotNull(filename2);
|
||||
assertEquals(token.toString(), filename2);
|
||||
String filename3 = manager.getPendingUpload(txn, contactId3);
|
||||
assertNull(filename3);
|
||||
String filename4 =
|
||||
manager.getPendingUpload(txn, new ContactId(random.nextInt()));
|
||||
assertNull(filename4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingPendingUploads() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
// setting a pending upload stores expected settings
|
||||
Settings expectedSettings1 = new Settings();
|
||||
expectedSettings1.put(String.valueOf(contactId1.getInt()), onion);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings1,
|
||||
SETTINGS_UPLOADS_NAMESPACE);
|
||||
}});
|
||||
manager.setPendingUpload(txn, contactId1, onion);
|
||||
|
||||
// nulling a pending upload empties stored settings
|
||||
Settings expectedSettings2 = new Settings();
|
||||
expectedSettings2.put(String.valueOf(contactId2.getInt()), "");
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(settingsManager).mergeSettings(txn, expectedSettings2,
|
||||
SETTINGS_UPLOADS_NAMESPACE);
|
||||
}});
|
||||
manager.setPendingUpload(txn, contactId2, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.RunAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -69,7 +69,7 @@ public class PollerImplTest extends BrambleMockTestCase {
|
||||
private PollerImpl poller;
|
||||
|
||||
public PollerImplTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||
random = context.mock(SecureRandom.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
|
||||
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.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
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.MEEK_BRIDGES;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CircumventionProviderTest extends BrambleTestCase {
|
||||
|
||||
private final CircumventionProvider provider =
|
||||
new CircumventionProviderImpl();
|
||||
|
||||
@Test
|
||||
public void testInvariants() {
|
||||
Set<String> blocked = new HashSet<>(asList(BLOCKED));
|
||||
Set<String> bridges = new HashSet<>(asList(BRIDGES));
|
||||
Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||
Set<String> nonDefaultBridges =
|
||||
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
|
||||
// BRIDGES should be a subset of BLOCKED
|
||||
assertTrue(blocked.containsAll(bridges));
|
||||
// BRIDGES should be the union of the bridge type sets
|
||||
Set<String> union = new HashSet<>(defaultBridges);
|
||||
union.addAll(nonDefaultBridges);
|
||||
union.addAll(meekBridges);
|
||||
assertEquals(bridges, union);
|
||||
// The bridge type sets should not overlap
|
||||
assertEmptyIntersection(defaultBridges, nonDefaultBridges);
|
||||
assertEmptyIntersection(defaultBridges, meekBridges);
|
||||
assertEmptyIntersection(nonDefaultBridges, meekBridges);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBestBridgeType() {
|
||||
for (String country : DEFAULT_BRIDGES) {
|
||||
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
for (String country : NON_DEFAULT_BRIDGES) {
|
||||
assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
for (String country : MEEK_BRIDGES) {
|
||||
assertEquals(singletonList(MEEK),
|
||||
provider.getSuitableBridgeTypes(country));
|
||||
}
|
||||
assertEquals(asList(DEFAULT_OBFS4, VANILLA),
|
||||
provider.getSuitableBridgeTypes("ZZ"));
|
||||
}
|
||||
|
||||
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
|
||||
Set<T> intersection = new HashSet<>(a);
|
||||
intersection.retainAll(b);
|
||||
assertTrue(intersection.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,9 @@ import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.CommitAction;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.EventAction;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.event.RemoteTransportPropertiesUpdatedEvent;
|
||||
@@ -48,6 +45,7 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.hasEvent;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -855,15 +853,4 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
false);
|
||||
}});
|
||||
}
|
||||
|
||||
private boolean hasEvent(Transaction txn,
|
||||
Class<? extends Event> eventClass) {
|
||||
for (CommitAction action : txn.getActions()) {
|
||||
if (action instanceof EventAction) {
|
||||
Event event = ((EventAction) action).getEvent();
|
||||
if (eventClass.isInstance(event)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,26 @@ public class TestFeatureFlagModule {
|
||||
public boolean shouldEnableDisappearingMessages() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableMailbox() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnablePrivateGroupsInCore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableForumsInCore() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableBlogsInCore() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +393,76 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
assertNull(transportKeyManager.getStreamContext(txn, tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStreamContextOnlyAndMarkTag() throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
// Keep a copy of the tags
|
||||
List<byte[]> tags = new ArrayList<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
|
||||
1000, alice, true);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (the start of time period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(timePeriodLength * 1000));
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(transportCrypto).encodeTag(
|
||||
with(any(byte[].class)), with(tagKey),
|
||||
with(PROTOCOL_VERSION), with(i));
|
||||
will(new EncodeTagAction(tags));
|
||||
}
|
||||
// Updated the transport keys (the keys are unaffected)
|
||||
oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
|
||||
will(returnValue(transportKeys));
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||
will(returnValue(keySetId));
|
||||
// Encode a new tag after sliding the window
|
||||
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION),
|
||||
with((long) REORDERING_WINDOW_SIZE));
|
||||
will(new EncodeTagAction(tags));
|
||||
// Save the reordering window (previous time period, base 1)
|
||||
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
|
||||
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||
}});
|
||||
|
||||
// The timestamp is at the start of time period 1000
|
||||
long timestamp = timePeriodLength * 1000;
|
||||
assertEquals(keySetId, transportKeyManager.addRotationKeys(
|
||||
txn, contactId, rootKey, timestamp, alice, true));
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
// Use the first tag (previous time period, stream number 0)
|
||||
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
|
||||
byte[] tag = tags.get(0);
|
||||
// Repeated request should return same stream context
|
||||
StreamContext ctx = transportKeyManager.getStreamContextOnly(txn, tag);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertEquals(tagKey, ctx.getTagKey());
|
||||
assertEquals(headerKey, ctx.getHeaderKey());
|
||||
assertEquals(0L, ctx.getStreamNumber());
|
||||
ctx = transportKeyManager.getStreamContextOnly(txn, tag);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertEquals(tagKey, ctx.getTagKey());
|
||||
assertEquals(headerKey, ctx.getHeaderKey());
|
||||
assertEquals(0L, ctx.getStreamNumber());
|
||||
// Then mark tag as recognised
|
||||
transportKeyManager.markTagAsRecognised(txn, tag);
|
||||
// Another tag should have been encoded
|
||||
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
|
||||
// Finally ensure the used tag is not recognised again
|
||||
assertNull(transportKeyManager.getStreamContextOnly(txn, tag));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreUpdatedToCurrentPeriod() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
|
||||
'com.fasterxml.jackson.core:jackson-annotations:2.13.0:jackson-annotations-2.13.0.jar:81f9724d8843e8b08f8f6c0609e7a2b030d00c34861c4ac7e2099a7235047d6f',
|
||||
'com.fasterxml.jackson.core:jackson-core:2.13.0:jackson-core-2.13.0.jar:348bc59b348df2e807b356f1d62d2afb41a974073328abc773eb0932b855d2c8',
|
||||
'com.fasterxml.jackson.core:jackson-databind:2.13.0:jackson-databind-2.13.0.jar:9c826d27176268777adcf97e1c6e2051c7e33a7aaa2c370c2e8c6077fd9da3f4',
|
||||
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
|
||||
@@ -15,6 +18,11 @@ dependencyVerification {
|
||||
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
|
||||
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.squareup.okhttp3:mockwebserver:4.9.3:mockwebserver-4.9.3.jar:9c8c581c29f22f877a35d11380462f75bb24bf1886204fe835ee695594a2784e',
|
||||
'com.squareup.okhttp3:okhttp:3.12.13:okhttp-3.12.13.jar:508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82',
|
||||
'com.squareup.okhttp3:okhttp:4.9.3:okhttp-4.9.3.jar:93ecd6cba19d87dccfe566ec848d91aae799e3cf16c00709358ea69bd9227219',
|
||||
'com.squareup.okio:okio:1.15.0:okio-1.15.0.jar:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2',
|
||||
'com.squareup.okio:okio:2.8.0:okio-jvm-2.8.0.jar:4496b06e73982fcdd8a5393f46e5df2ce2fa4465df5895454cac68a32f09bbc8',
|
||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
@@ -26,7 +34,7 @@ dependencyVerification {
|
||||
'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.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
|
||||
'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38',
|
||||
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
|
||||
'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',
|
||||
@@ -38,7 +46,11 @@ dependencyVerification {
|
||||
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
||||
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
||||
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10:kotlin-stdlib-common-1.4.10.jar:4681f2d436a68c7523595d84ed5758e1382f9da0f67c91e6a848690d711274fe',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10:kotlin-stdlib-jdk7-1.4.10.jar:f9566380c08722c780ce33ceee23e98ddf765ca98fabd3e2fabae7975c8d232b',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10:kotlin-stdlib-jdk8-1.4.10.jar:39b7a9442d7a3865e0f4a732c56c1d5da0e11ffb3bb82a461d32deb0c0ca7673',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.4.10:kotlin-stdlib-1.4.10.jar:01ecb09782c042b931c1839acf21a188340b295d05400afd6e3415d4475b8daa',
|
||||
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
|
||||
'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',
|
||||
|
||||
@@ -17,8 +17,8 @@ dependencies {
|
||||
def jna_version = '4.5.2'
|
||||
implementation "net.java.dev.jna:jna:$jna_version"
|
||||
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
||||
tor "org.briarproject:tor:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy:$obfs4proxy_version@zip"
|
||||
tor "org.briarproject:tor-linux:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
@@ -27,7 +27,6 @@ dependencies {
|
||||
testImplementation "junit:junit:$junit_version"
|
||||
testImplementation "org.jmock:jmock:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmock_version"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmock_version"
|
||||
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||
import org.briarproject.bramble.api.Multiset;
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.network.NetworkManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
||||
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
|
||||
@@ -26,19 +29,25 @@ import org.junit.runners.Parameterized.Parameters;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
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.MEEK;
|
||||
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||
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.getTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||
@@ -55,17 +64,31 @@ public class BridgeTest extends BrambleTestCase {
|
||||
DaggerBrambleJavaIntegrationTestComponent.builder().build();
|
||||
BrambleCoreIntegrationTestEagerSingletons.Helper
|
||||
.injectEagerSingletons(component);
|
||||
// Share a failure counter among all the test instances
|
||||
AtomicInteger failures = new AtomicInteger(0);
|
||||
List<String> bridges =
|
||||
component.getCircumventionProvider().getBridges(false);
|
||||
List<Params> states = new ArrayList<>(bridges.size());
|
||||
for (String bridge : bridges) states.add(new Params(bridge, failures));
|
||||
// Share stats among all the test instances
|
||||
Stats stats = new Stats();
|
||||
CircumventionProvider provider = component.getCircumventionProvider();
|
||||
List<Params> states = new ArrayList<>();
|
||||
for (int i = 0; i < ATTEMPTS_PER_BRIDGE; i++) {
|
||||
for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
|
||||
states.add(new Params(bridge, DEFAULT_OBFS4, stats, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
|
||||
states.add(new Params(bridge, NON_DEFAULT_OBFS4, stats, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(VANILLA)) {
|
||||
states.add(new Params(bridge, VANILLA, stats, false));
|
||||
}
|
||||
for (String bridge : provider.getBridges(MEEK)) {
|
||||
states.add(new Params(bridge, MEEK, stats, true));
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
private final static long TIMEOUT = SECONDS.toMillis(60);
|
||||
private final static int NUM_FAILURES_ALLOWED = 1;
|
||||
private final static long OBFS4_TIMEOUT = MINUTES.toMillis(2);
|
||||
private final static long MEEK_TIMEOUT = MINUTES.toMillis(6);
|
||||
private final static int UNREACHABLE_BRIDGES_ALLOWED = 6;
|
||||
private final static int ATTEMPTS_PER_BRIDGE = 5;
|
||||
|
||||
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
||||
|
||||
@@ -80,8 +103,6 @@ public class BridgeTest extends BrambleTestCase {
|
||||
@Inject
|
||||
ResourceProvider resourceProvider;
|
||||
@Inject
|
||||
CircumventionProvider circumventionProvider;
|
||||
@Inject
|
||||
BatteryManager batteryManager;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
@@ -93,14 +114,12 @@ public class BridgeTest extends BrambleTestCase {
|
||||
CryptoComponent crypto;
|
||||
|
||||
private final File torDir = getTestDirectory();
|
||||
private final String bridge;
|
||||
private final AtomicInteger failures;
|
||||
private final Params params;
|
||||
|
||||
private UnixTorPluginFactory factory;
|
||||
|
||||
public BridgeTest(Params params) {
|
||||
bridge = params.bridge;
|
||||
failures = params.failures;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -120,6 +139,7 @@ public class BridgeTest extends BrambleTestCase {
|
||||
LocationUtils locationUtils = () -> "US";
|
||||
SocketFactory torSocketFactory = SocketFactory.getDefault();
|
||||
|
||||
@NotNullByDefault
|
||||
CircumventionProvider bridgeProvider = new CircumventionProvider() {
|
||||
@Override
|
||||
public boolean isTorProbablyBlocked(String countryCode) {
|
||||
@@ -132,13 +152,13 @@ public class BridgeTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsMeek(String countryCode) {
|
||||
return false;
|
||||
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
|
||||
return singletonList(params.bridgeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getBridges(boolean useMeek) {
|
||||
return singletonList(bridge);
|
||||
public List<String> getBridges(BridgeType bridgeType) {
|
||||
return singletonList(params.bridge);
|
||||
}
|
||||
};
|
||||
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
|
||||
@@ -160,19 +180,20 @@ public class BridgeTest extends BrambleTestCase {
|
||||
assertNotNull(duplexPlugin);
|
||||
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
|
||||
|
||||
LOG.warning("Testing " + bridge);
|
||||
LOG.warning("Testing " + params.bridge);
|
||||
try {
|
||||
plugin.start();
|
||||
long start = clock.currentTimeMillis();
|
||||
while (clock.currentTimeMillis() - start < TIMEOUT) {
|
||||
long timeout = params.bridgeType == MEEK
|
||||
? MEEK_TIMEOUT : OBFS4_TIMEOUT;
|
||||
while (clock.currentTimeMillis() - start < timeout) {
|
||||
if (plugin.getState() == ACTIVE) return;
|
||||
clock.sleep(500);
|
||||
}
|
||||
if (plugin.getState() != ACTIVE) {
|
||||
LOG.warning("Could not connect to Tor within timeout");
|
||||
if (failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
|
||||
fail(failures.get() + " bridges are unreachable");
|
||||
}
|
||||
LOG.warning("Could not connect to Tor within timeout: "
|
||||
+ params.bridge);
|
||||
params.stats.countFailure(params.bridge, params.essential);
|
||||
}
|
||||
} finally {
|
||||
plugin.stop();
|
||||
@@ -182,11 +203,40 @@ public class BridgeTest extends BrambleTestCase {
|
||||
private static class Params {
|
||||
|
||||
private final String bridge;
|
||||
private final AtomicInteger failures;
|
||||
private final BridgeType bridgeType;
|
||||
private final Stats stats;
|
||||
private final boolean essential;
|
||||
|
||||
private Params(String bridge, AtomicInteger failures) {
|
||||
private Params(String bridge, BridgeType bridgeType,
|
||||
Stats stats, boolean essential) {
|
||||
this.bridge = bridge;
|
||||
this.failures = failures;
|
||||
this.bridgeType = bridgeType;
|
||||
this.stats = stats;
|
||||
this.essential = essential;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Stats {
|
||||
|
||||
@GuardedBy("this")
|
||||
private final Multiset<String> failures = new Multiset<>();
|
||||
@GuardedBy("this")
|
||||
private final Set<String> unreachable = new TreeSet<>();
|
||||
|
||||
private synchronized void countFailure(String bridge,
|
||||
boolean essential) {
|
||||
if (failures.add(bridge) == ATTEMPTS_PER_BRIDGE) {
|
||||
LOG.warning("Bridge is unreachable after "
|
||||
+ ATTEMPTS_PER_BRIDGE + " attempts: " + bridge);
|
||||
unreachable.add(bridge);
|
||||
if (unreachable.size() > UNREACHABLE_BRIDGES_ALLOWED) {
|
||||
fail(unreachable.size() + " bridges are unreachable: "
|
||||
+ unreachable);
|
||||
}
|
||||
if (essential) {
|
||||
fail("essential bridge is unreachable");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ dependencyVerification {
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'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.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
|
||||
'org.briarproject:tor:0.3.5.17:tor-0.3.5.17.jar:ce0e1f4d8f14878e61b23a35a452bc0f2a8e3117ced5a74773cd78475fa7af39',
|
||||
'org.briarproject:obfs4proxy-linux:0.0.12:obfs4proxy-linux-0.0.12.jar:3dd83aff25fe1cb3e4eab78a02c76ac921f552be6877b3af83a472438525df2a',
|
||||
'org.briarproject:tor-linux:0.4.5.12-2:tor-linux-0.4.5.12-2.jar:d275f323faf5e70b33d2c8a1bdab1bb3ab5a0d8f4e23c4a6dda03d86f4e95838',
|
||||
'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.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||
|
||||
8
briar-android/.gitignore
vendored
8
briar-android/.gitignore
vendored
@@ -11,5 +11,9 @@ src/main/res/values-iw
|
||||
/fastlane/metadata/android/*/images
|
||||
/fastlane/report.xml
|
||||
/fastlane/README.md
|
||||
|
||||
/fastlane/metadata/android/*/changelogs
|
||||
/fastlane/metadata/android/*/changelogs
|
||||
# Local Fastlane installation
|
||||
/fastlane/vendor
|
||||
/fastlane/.bundle
|
||||
/fastlane/Gemfile
|
||||
/fastlane/Gemfile.lock
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN, zh-Hant: zh-rTW
|
||||
|
||||
[briar.stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
source_file = src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
[o:otf:p:briar:r:google-play-full-description]
|
||||
file_filter = fastlane/metadata/android/<lang>/full_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/full_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
# https://support.google.com/googleplay/android-developer/answer/9844778?hl=en#zippy=%2Cview-list-of-available-languages
|
||||
lang_map = de: de-DE, is: is-IS, ja: ja-JP, tr: tr-TR, zh-Hans: zh-CN, ru: ru-RU, sv: sv-SE, hu: hu-HU, it: it-IT, my: my-MM, en: en-US, es: es-ES, fr: fr-FR, gl: gl-ES, pl: pl-PL, el: el-GR
|
||||
|
||||
[o:otf:p:briar:r:google-play-short-description]
|
||||
file_filter = fastlane/metadata/android/<lang>/short_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/short_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
lang_map = en: en-US, es: es-ES, is: is-IS, tr: tr-TR, gl: gl-ES, it: it-IT, my: my-MM, sv: sv-SE, zh-Hans: zh-CN, de: de-DE, ja: ja-JP, ru: ru-RU, fr: fr-FR, hu: hu-HU, pl: pl-PL, el: el-GR
|
||||
|
||||
[o:otf:p:briar:r:stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
source_file = src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 80
|
||||
|
||||
[briar.google-play-short-description]
|
||||
# https://support.google.com/googleplay/android-developer/answer/9844778?hl=en#zippy=%2Cview-list-of-available-languages
|
||||
lang_map = en: en-US, de: de-DE, es: es-ES, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
|
||||
file_filter = fastlane/metadata/android/<lang>/short_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/short_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
|
||||
[briar.google-play-full-description]
|
||||
lang_map = en: en-US, de: de-DE, es: es-ES, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
|
||||
file_filter = fastlane/metadata/android/<lang>/full_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/full_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
|
||||
16
briar-android/artwork/ic_mailbox.svg
Normal file
16
briar-android/artwork/ic_mailbox.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" version="1.1" id="svg4"
|
||||
sodipodi:docname="ic_mailbox.svg" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs id="defs8" />
|
||||
<sodipodi:namedview id="namedview6" pagecolor="#7d7d7d" bordercolor="#666666" borderopacity="1.0"
|
||||
inkscape:pageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0"
|
||||
showgrid="false" inkscape:zoom="9.0780032" inkscape:cx="22.086355" inkscape:cy="10.850404"
|
||||
inkscape:window-width="1920" inkscape:window-height="928" inkscape:window-x="0"
|
||||
inkscape:window-y="108" inkscape:window-maximized="1" inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="m 5.0781637,2.0000001 c -0.5245356,0 -1.0278314,0.2105185 -1.3987365,0.5847785 C 3.3085215,2.9590375 3.1,3.4663153 3.1,3.9956004 V 16.767666 c 0,0.529289 0.2085215,1.037692 0.5794272,1.411969 0.3709051,0.37423 0.8742009,0.583635 1.3987365,0.583635 H 15.850874 l 3.588966,3.026135 C 20.019297,22.277986 20.9,21.862034 20.9,21.100087 V 3.9956004 C 20.9,3.4663153 20.691473,2.9590375 20.32057,2.5847786 19.949681,2.2105186 19.44635,2.0000001 18.921831,2.0000001 Z M 6.2648311,5.19273 H 17.73517 v 5.65018 h -4.00732 v 1.954238 h 1.465946 c 0.26433,0 0.396594,0.32267 0.209762,0.511253 l -3.160194,3.188138 c -0.11588,0.116871 -0.303625,0.116871 -0.419503,0 L 8.6636588,13.308401 C 8.4767702,13.119818 8.6091068,12.797148 8.8734113,12.797148 H 10.339359 V 10.84291 H 6.2648311 Z"
|
||||
fill="white" id="path2" style="fill:#ffffff;stroke:none;stroke-width:0.590769" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
105
briar-android/artwork/mailbox_onboarding_dark.svg
Normal file
105
briar-android/artwork/mailbox_onboarding_dark.svg
Normal file
@@ -0,0 +1,105 @@
|
||||
<svg width="250" height="199" viewBox="0 0 250 199" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M95.4363 100.233C95.4363 93.5233 100.876 88.084 107.585 88.084H130.365C137.074 88.084 142.514 93.5233 142.514 100.233V123.012C142.514 129.722 137.074 135.161 130.365 135.161H107.585C100.876 135.161 95.4363 129.722 95.4363 123.012V100.233Z"
|
||||
fill="#15212D" />
|
||||
<path
|
||||
d="M102.336 10.2609C102.336 4.59395 106.93 0 112.597 0H131.836C137.503 0 142.097 4.59395 142.097 10.2609V29.5C142.097 35.1669 137.503 39.7609 131.836 39.7609H112.597C106.93 39.7609 102.336 35.1669 102.336 29.5V10.2609Z"
|
||||
fill="#15212D" />
|
||||
<path
|
||||
d="M34.8184 148.09C34.8184 143.427 38.5986 139.646 43.2618 139.646H59.0933C63.7565 139.646 67.5368 143.427 67.5368 148.09V163.921C67.5368 168.585 63.7565 172.365 59.0933 172.365H43.2618C38.5986 172.365 34.8184 168.585 34.8184 163.921V148.09Z"
|
||||
fill="#15212D" />
|
||||
<path
|
||||
d="M110.024 186.044C110.024 183.557 112.04 181.541 114.527 181.541H122.971C125.458 181.541 127.475 183.557 127.475 186.044V194.488C127.475 196.976 125.458 198.992 122.971 198.992H114.527C112.04 198.992 110.024 196.976 110.024 194.488V186.044Z"
|
||||
fill="#15212D" />
|
||||
<path
|
||||
d="M173.713 141.6C173.713 134.89 179.152 129.451 185.862 129.451H208.641C215.351 129.451 220.79 134.89 220.79 141.6V164.38C220.79 171.089 215.351 176.528 208.641 176.528H185.862C179.152 176.528 173.713 171.089 173.713 164.38V141.6Z"
|
||||
fill="#15212D" />
|
||||
<path
|
||||
d="M232.049 95.4156C232.049 92.9284 234.065 90.9121 236.553 90.9121H244.997C247.484 90.9121 249.5 92.9284 249.5 95.4156V103.86C249.5 106.347 247.484 108.363 244.997 108.363H236.553C234.065 108.363 232.049 106.347 232.049 103.86V95.4156Z"
|
||||
fill="#15212D" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M120.481 84.2273V45.2676H122.481V84.2273H120.481Z" fill="#657D99" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M117.685 176.101L117.685 139.646H119.685L119.685 176.101H117.685Z" fill="#657D99" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M3.27959 108.445L4.0457 106.598C4.3381 106.719 4.66032 106.787 5.00345 106.787H7.11444V108.787H5.00345C4.39282 108.787 3.81058 108.665 3.27959 108.445ZM11.3364 108.787V106.787H13.4474C13.7905 106.787 14.1128 106.719 14.4052 106.598L15.1713 108.445C14.6403 108.665 14.058 108.787 13.4474 108.787H11.3364ZM17.9509 97.9504H15.9509V95.8394C15.9509 95.4963 15.8829 95.174 15.7617 94.8816L17.6091 94.1155C17.8293 94.6465 17.9509 95.2288 17.9509 95.8394V97.9504ZM7.11444 91.3359H5.00345C4.39282 91.3359 3.81058 91.4575 3.27959 91.6777L4.0457 93.5251C4.3381 93.4039 4.66032 93.3359 5.00345 93.3359H7.11444V91.3359ZM0.500008 102.172H2.50001V104.283C2.50001 104.626 2.56793 104.949 2.68918 105.241L0.841732 106.007C0.621538 105.476 0.500008 104.894 0.500008 104.283V102.172ZM0.500008 97.9504H2.50001V95.8394C2.50001 95.4963 2.56793 95.174 2.68918 94.8816L0.841731 94.1155C0.621538 94.6465 0.500008 95.2288 0.500008 95.8394V97.9504ZM11.3364 91.3359V93.3359H13.4474C13.7905 93.3359 14.1128 93.4039 14.4052 93.5251L15.1713 91.6777C14.6403 91.4575 14.058 91.3359 13.4474 91.3359H11.3364ZM17.9509 102.172H15.9509V104.283C15.9509 104.626 15.8829 104.949 15.7617 105.241L17.6091 106.007C17.8293 105.476 17.9509 104.894 17.9509 104.283V102.172Z"
|
||||
fill="#657D99" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M41.1771 64.5169V62.6484H43.1771V64.5169H41.1771ZM41.1771 71.9908V68.2538H43.1771V71.9908H41.1771ZM41.1771 79.4647V75.7277H43.1771V79.4647H41.1771ZM41.1771 86.9385V83.2016H43.1771V86.9385H41.1771ZM41.1771 94.4124V90.6755H43.1771V94.4124H41.1771ZM41.1771 99.0178V98.1493H43.1771V99.0178H44.9095V101.018H39.681V99.0178H41.1771ZM24.7048 101.018H22.2087V99.0178H24.7048V101.018ZM34.6889 101.018H29.6969V99.0178H34.6889V101.018ZM52.1068 101.018H50.3744V99.0178H54.8543V101.018H54.1068V102.148H52.1068V101.018ZM61.8442 101.018H58.3493V99.0178H61.8442V101.018ZM67.0867 101.018H65.3392V99.0178H67.0867C68.0476 99.0178 68.9663 99.2092 69.8048 99.557L69.0387 101.404C68.4388 101.156 67.7801 101.018 67.0867 101.018ZM72.187 106.118C72.187 105.425 72.0492 104.766 71.8004 104.166L73.6478 103.4C73.9955 104.239 74.187 105.157 74.187 106.118C74.187 106.812 74.3248 107.47 74.5736 108.07L72.7261 108.836C72.3784 107.998 72.187 107.079 72.187 106.118ZM52.1068 110.668V106.408H54.1068V110.668H52.1068ZM79.2873 113.218C78.3264 113.218 77.4077 113.027 76.5691 112.679L77.3352 110.832C77.9352 111.081 78.5939 111.218 79.2873 111.218H81.5905V113.218H79.2873ZM88.5 113.218H86.1968V111.218H88.5V113.218ZM52.1068 119.188V114.928H54.1068V119.188H52.1068ZM52.1068 127.709V123.449H54.1068V127.709H52.1068ZM52.1068 134.099V131.969H54.1068V134.099H52.1068Z"
|
||||
fill="#74B816" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M105.214 134.93L105.602 132.968C106.242 133.095 106.905 133.161 107.585 133.161H109.484V135.161H107.585C106.774 135.161 105.981 135.082 105.214 134.93ZM128.466 135.161V133.161H130.365C131.045 133.161 131.708 133.095 132.347 132.968L132.736 134.93C131.969 135.082 131.176 135.161 130.365 135.161H128.466ZM137.115 133.115L136.002 131.453C137.11 130.712 138.064 129.757 138.805 128.65L140.467 129.762C139.58 131.087 138.44 132.228 137.115 133.115ZM142.514 102.131H140.514V100.233C140.514 99.5526 140.447 98.8898 140.32 98.2501L142.282 97.8619C142.434 98.6288 142.514 99.4216 142.514 100.233V102.131ZM140.467 93.4828L138.805 94.5954C138.064 93.4878 137.11 92.5338 136.002 91.7922L137.115 90.1303C138.44 91.0173 139.58 92.1579 140.467 93.4828ZM109.484 88.084H107.585C106.774 88.084 105.981 88.1635 105.214 88.3152L105.602 90.2772C106.242 90.1507 106.905 90.084 107.585 90.084H109.484V88.084ZM100.835 90.1303L101.948 91.7922C100.84 92.5338 99.8861 93.4878 99.1445 94.5954L97.4826 93.4828C98.3696 92.1579 99.5102 91.0173 100.835 90.1303ZM95.4363 121.114H97.4363V123.012C97.4363 123.693 97.503 124.355 97.6295 124.995L95.6675 125.383C95.5158 124.616 95.4363 123.824 95.4363 123.012V121.114ZM97.4826 129.762L99.1445 128.65C99.8861 129.757 100.84 130.712 101.948 131.453L100.835 133.115C99.5102 132.228 98.3696 131.087 97.4826 129.762ZM95.4363 117.317H97.4363V113.521H95.4363V117.317ZM95.4363 109.724H97.4363V105.928H95.4363V109.724ZM95.4363 102.131H97.4363V100.233C97.4363 99.5526 97.503 98.8898 97.6295 98.2501L95.6675 97.8619C95.5158 98.6288 95.4363 99.4216 95.4363 100.233V102.131ZM113.28 88.084V90.084H117.077V88.084H113.28ZM120.873 88.084V90.084H124.67V88.084H120.873ZM128.466 88.084V90.084H130.365C131.045 90.084 131.708 90.1507 132.347 90.2772L132.736 88.3152C131.969 88.1635 131.176 88.084 130.365 88.084H128.466ZM142.514 105.928H140.514V109.724H142.514V105.928ZM142.514 113.521H140.514V117.317H142.514V113.521ZM142.514 121.114H140.514V123.012C140.514 123.693 140.447 124.355 140.32 124.995L142.282 125.383C142.434 124.616 142.514 123.824 142.514 123.012V121.114ZM124.67 135.161V133.161H120.873V135.161H124.67ZM117.077 135.161V133.161H113.28V135.161H117.077Z"
|
||||
fill="#74B816" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M188.618 86.5389V84.6738H190.618V86.5389H188.618ZM188.618 93.9991V90.269H190.618V93.9991H188.618ZM188.618 97.7293H190.618V98.5943H191.517V100.594H190.618V101.183H188.618V97.7293ZM199.112 100.594H195.315V98.5943H199.112V100.594ZM206.707 100.594H202.91V98.5943H206.707V100.594ZM214.303 100.594H210.505V98.5943H214.303V100.594ZM221.898 100.594H218.1V98.5943H221.898V100.594ZM227.595 100.594H225.696V98.5943H227.595V100.594ZM188.618 107.537V104.36H190.618V107.537H188.618ZM188.618 111.303V110.715H190.618V111.359C190.865 111.387 191.109 111.425 191.35 111.472L190.962 113.434C190.528 113.349 190.079 113.303 189.618 113.303H187.737V111.303H188.618ZM153.881 113.303H152V111.303H153.881V113.303ZM161.405 113.303H157.643V111.303H161.405V113.303ZM168.928 113.303H165.166V111.303H168.928V113.303ZM176.452 113.303H172.69V111.303H176.452V113.303ZM183.975 113.303H180.214V111.303H183.975V113.303ZM195.34 116.361C194.837 115.611 194.19 114.964 193.44 114.462L194.552 112.8C195.52 113.448 196.353 114.281 197.001 115.249L195.34 116.361ZM196.498 120.183C196.498 119.722 196.453 119.273 196.367 118.839L198.329 118.451C198.44 119.012 198.498 119.591 198.498 120.183V121.203H196.498V120.183ZM196.498 124.261V123.242H198.498V124.261H196.498Z"
|
||||
fill="#74B816" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M183.491 176.297L183.879 174.335C184.519 174.462 185.182 174.528 185.862 174.528H187.76V176.528H185.862C185.051 176.528 184.258 176.449 183.491 176.297ZM206.743 176.528V174.528H208.641C209.322 174.528 209.985 174.462 210.624 174.335L211.012 176.297C210.246 176.449 209.453 176.528 208.641 176.528H206.743ZM215.392 174.482L214.279 172.82C215.387 172.079 216.341 171.125 217.082 170.017L218.744 171.13C217.857 172.455 216.717 173.595 215.392 174.482ZM220.79 143.498H218.79V141.6C218.79 140.92 218.724 140.257 218.597 139.617L220.559 139.229C220.711 139.996 220.79 140.789 220.79 141.6V143.498ZM218.744 134.85L217.082 135.963C216.341 134.855 215.387 133.901 214.279 133.159L215.392 131.497C216.717 132.384 217.857 133.525 218.744 134.85ZM187.76 129.451H185.862C185.051 129.451 184.258 129.531 183.491 129.682L183.879 131.644C184.519 131.518 185.182 131.451 185.862 131.451H187.76V129.451ZM179.112 131.497L180.225 133.159C179.117 133.901 178.163 134.855 177.421 135.963L175.759 134.85C176.646 133.525 177.787 132.384 179.112 131.497ZM173.713 162.481H175.713V164.38C175.713 165.06 175.78 165.723 175.906 166.362L173.944 166.751C173.793 165.984 173.713 165.191 173.713 164.38V162.481ZM175.759 171.13L177.421 170.017C178.163 171.125 179.117 172.079 180.225 172.82L179.112 174.482C177.787 173.595 176.646 172.455 175.759 171.13ZM173.713 158.685H175.713V154.888H173.713V158.685ZM173.713 151.092H175.713V147.295H173.713V151.092ZM173.713 143.498H175.713V141.6C175.713 140.92 175.78 140.257 175.906 139.617L173.944 139.229C173.793 139.996 173.713 140.789 173.713 141.6V143.498ZM191.557 129.451V131.451H195.354V129.451H191.557ZM199.15 129.451V131.451H202.947V129.451H199.15ZM206.743 129.451V131.451H208.641C209.322 131.451 209.985 131.518 210.624 131.644L211.012 129.682C210.246 129.531 209.453 129.451 208.641 129.451H206.743ZM220.79 147.295H218.79V151.092H220.79V147.295ZM220.79 154.888H218.79V158.685H220.79V154.888ZM220.79 162.481H218.79V164.38C218.79 165.06 218.724 165.723 218.597 166.362L220.559 166.751C220.711 165.984 220.79 165.191 220.79 164.38V162.481ZM202.947 176.528V174.528H199.15V176.528H202.947ZM195.354 176.528V174.528H191.557V176.528H195.354Z"
|
||||
fill="#74B816" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M110.594 39.5656L110.982 37.6036C111.503 37.7066 112.042 37.7609 112.597 37.7609H115.001V39.7609H112.597C111.911 39.7609 111.242 39.6937 110.594 39.5656ZM129.431 39.7609V37.7609H131.836C132.39 37.7609 132.93 37.7066 133.45 37.6036L133.838 39.5656C133.191 39.6937 132.521 39.7609 131.836 39.7609H129.431ZM137.537 38.0326L136.424 36.3706C137.326 35.767 138.103 34.9902 138.706 34.0885L140.368 35.2011C139.619 36.3201 138.656 37.2835 137.537 38.0326ZM142.097 12.6658H140.097V10.2609C140.097 9.7066 140.042 9.167 139.939 8.64647L141.901 8.25834C142.029 8.90601 142.097 9.57559 142.097 10.2609V12.6658ZM140.368 4.55977L138.706 5.67241C138.103 4.77069 137.326 3.99391 136.424 3.39022L137.537 1.72828C138.656 2.47741 139.619 3.44081 140.368 4.55977ZM115.001 0H112.597C111.911 0 111.242 0.0671774 110.594 0.195306L110.982 2.15728C111.503 2.05431 112.042 2 112.597 2H115.001V0ZM106.895 1.72829L108.008 3.39022C107.106 3.99391 106.33 4.77069 105.726 5.67241L104.064 4.55977C104.813 3.44081 105.777 2.47741 106.895 1.72829ZM102.336 27.0951H104.336V29.5C104.336 30.0543 104.39 30.5939 104.493 31.1144L102.531 31.5025C102.403 30.8549 102.336 30.1853 102.336 29.5V27.0951ZM104.064 35.2011L105.726 34.0885C106.33 34.9902 107.106 35.767 108.008 36.3706L106.895 38.0326C105.777 37.2835 104.813 36.3201 104.064 35.2011ZM102.336 22.2853H104.336V17.4755H102.336V22.2853ZM102.336 12.6658H104.336V10.2609C104.336 9.7066 104.39 9.167 104.493 8.64647L102.531 8.25834C102.403 8.90601 102.336 9.57559 102.336 10.2609V12.6658ZM119.811 0V2H124.621V0H119.811ZM129.431 0V2H131.836C132.39 2 132.93 2.05431 133.45 2.15728L133.838 0.195306C133.191 0.0671773 132.521 0 131.836 0H129.431ZM142.097 17.4755H140.097V22.2853H142.097V17.4755ZM142.097 27.0951H140.097V29.5C140.097 30.0543 140.042 30.5939 139.939 31.1144L141.901 31.5025C142.029 30.8549 142.097 30.1853 142.097 29.5V27.0951ZM124.621 39.7609V37.7609H119.811V39.7609H124.621Z"
|
||||
fill="#435B77" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M40.0298 171.724L40.7959 169.877C41.5528 170.191 42.3845 170.365 43.2618 170.365H45.2408V172.365H43.2618C42.117 172.365 41.0253 172.137 40.0298 171.724ZM57.1144 172.365V170.365H59.0933C59.9707 170.365 60.8023 170.191 61.5593 169.877L62.3254 171.724C61.3298 172.137 60.2382 172.365 59.0933 172.365H57.1144ZM67.5368 150.069H65.5368V148.09C65.5368 147.213 65.3625 146.381 65.0486 145.624L66.8961 144.858C67.3089 145.853 67.5368 146.945 67.5368 148.09V150.069ZM45.2408 139.646H43.2618C42.117 139.646 41.0253 139.874 40.0298 140.287L40.7959 142.135C41.5528 141.821 42.3845 141.646 43.2618 141.646H45.2408V139.646ZM34.8184 161.943H36.8184V163.921C36.8184 164.799 36.9926 165.63 37.3065 166.387L35.4591 167.153C35.0462 166.158 34.8184 165.066 34.8184 163.921V161.943ZM34.8184 157.985H36.8184V154.027H34.8184V157.985ZM34.8184 150.069H36.8184V148.09C36.8184 147.213 36.9926 146.381 37.3065 145.624L35.4591 144.858C35.0462 145.853 34.8184 146.945 34.8184 148.09V150.069ZM49.1986 139.646V141.646H53.1565V139.646H49.1986ZM57.1144 139.646V141.646H59.0933C59.9707 141.646 60.8023 141.821 61.5593 142.135L62.3254 140.287C61.3298 139.874 60.2382 139.646 59.0933 139.646H57.1144ZM67.5368 154.027H65.5368V157.985H67.5368V154.027ZM67.5368 161.943H65.5368V163.921C65.5368 164.799 65.3625 165.63 65.0486 166.387L66.8961 167.154C67.3089 166.158 67.5368 165.066 67.5368 163.921V161.943ZM53.1565 172.365V170.365H49.1986V172.365H53.1565Z"
|
||||
fill="#435B77" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M112.803 198.65L113.569 196.803C113.862 196.924 114.184 196.992 114.527 196.992H116.638V198.992H114.527C113.917 198.992 113.334 198.87 112.803 198.65ZM120.86 198.992V196.992H122.971C123.314 196.992 123.636 196.924 123.929 196.803L124.695 198.65C124.164 198.87 123.582 198.992 122.971 198.992H120.86ZM127.475 188.155H125.475V186.044C125.475 185.701 125.407 185.379 125.285 185.087L127.133 184.321C127.353 184.852 127.475 185.434 127.475 186.044V188.155ZM116.638 181.541H114.527C113.917 181.541 113.334 181.663 112.803 181.883L113.569 183.73C113.862 183.609 114.184 183.541 114.527 183.541H116.638V181.541ZM110.024 192.377H112.024V194.488C112.024 194.832 112.092 195.154 112.213 195.446L110.365 196.212C110.145 195.681 110.024 195.099 110.024 194.488V192.377ZM110.024 188.155H112.024V186.044C112.024 185.701 112.092 185.379 112.213 185.087L110.365 184.321C110.145 184.852 110.024 185.434 110.024 186.044V188.155ZM120.86 181.541V183.541H122.971C123.314 183.541 123.636 183.609 123.929 183.73L124.695 181.883C124.164 181.663 123.582 181.541 122.971 181.541H120.86ZM127.475 192.377H125.475V194.488C125.475 194.832 125.407 195.154 125.285 195.446L127.133 196.212C127.353 195.681 127.475 195.099 127.475 194.488V192.377Z"
|
||||
fill="#435B77" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M178.996 79.8234L179.762 77.976C180.48 78.2737 181.269 78.4391 182.101 78.4391H184.003V80.4391H182.101C181.001 80.4391 179.952 80.2201 178.996 79.8234ZM195.412 80.4391V78.4391H197.314C198.146 78.4391 198.935 78.2737 199.653 77.976L200.419 79.8234C199.463 80.2201 198.414 80.4391 197.314 80.4391H195.412ZM205.427 59.0149H203.427V57.1133C203.427 56.2807 203.262 55.4918 202.964 54.7737L204.811 54.0076C205.208 54.9643 205.427 56.0132 205.427 57.1133V59.0149ZM184.003 49H182.101C181.001 49 179.952 49.2189 178.996 49.6156L179.762 51.4631C180.48 51.1653 181.269 51 182.101 51H184.003V49ZM173.988 70.4242H175.988V72.3258C175.988 73.1584 176.153 73.9473 176.451 74.6653L174.604 75.4315C174.207 74.4748 173.988 73.4259 173.988 72.3258V70.4242ZM173.988 66.6211H175.988V62.818H173.988V66.6211ZM173.988 59.0149H175.988V57.1133C175.988 56.2807 176.153 55.4918 176.451 54.7737L174.604 54.0076C174.207 54.9643 173.988 56.0132 173.988 57.1133V59.0149ZM187.806 49V51H191.609V49H187.806ZM195.412 49V51H197.314C198.146 51 198.935 51.1653 199.653 51.4631L200.419 49.6156C199.463 49.2189 198.414 49 197.314 49H195.412ZM205.427 62.818H203.427V66.6211H205.427V62.818ZM205.427 70.4242H203.427V72.3258C203.427 73.1584 203.262 73.9473 202.964 74.6653L204.811 75.4315C205.208 74.4748 205.427 73.4259 205.427 72.3258V70.4242ZM191.609 80.4391V78.4391H187.806V80.4391H191.609Z"
|
||||
fill="#657D99" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M234.829 108.021L235.595 106.174C235.887 106.295 236.209 106.363 236.553 106.363H238.664V108.363H236.553C235.942 108.363 235.36 108.241 234.829 108.021ZM242.886 108.363V106.363H244.997C245.34 106.363 245.662 106.295 245.954 106.174L246.72 108.021C246.189 108.241 245.607 108.363 244.997 108.363H242.886ZM249.5 97.5265H247.5V95.4156C247.5 95.0724 247.432 94.7502 247.311 94.4578L249.158 93.6917C249.378 94.2227 249.5 94.8049 249.5 95.4156V97.5265ZM238.664 90.9121H236.553C235.942 90.9121 235.36 91.0336 234.829 91.2538L235.595 93.1013C235.887 92.98 236.209 92.9121 236.553 92.9121H238.664V90.9121ZM232.049 101.749H234.049V103.86C234.049 104.203 234.117 104.525 234.238 104.817L232.391 105.583C232.171 105.052 232.049 104.47 232.049 103.86V101.749ZM232.049 97.5265H234.049V95.4156C234.049 95.0724 234.117 94.7502 234.238 94.4578L232.391 93.6917C232.171 94.2227 232.049 94.8049 232.049 95.4156V97.5265ZM242.886 90.9121V92.9121H244.997C245.34 92.9121 245.662 92.98 245.954 93.1013L246.72 91.2538C246.189 91.0336 245.607 90.9121 244.997 90.9121H242.886ZM249.5 101.749H247.5V103.86C247.5 104.203 247.432 104.525 247.311 104.817L249.158 105.583C249.378 105.052 249.5 104.47 249.5 103.86V101.749Z"
|
||||
fill="#435B77" />
|
||||
<path
|
||||
d="M29.8254 34.377V53.6604C29.8254 55.2256 31.1003 56.5166 32.6817 56.5166H34.021C35.5863 56.5166 36.8772 55.2256 36.8772 53.6604V34.377H29.8254Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M36.8611 25.0827V21.4519C36.8611 19.8866 35.5863 18.5957 34.021 18.5957H32.6817C31.1164 18.5957 29.8254 19.8866 29.8254 21.4519V25.0827H36.8611Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M52.5136 40.7353V21.4519C52.5136 19.8866 51.2388 18.5957 49.6735 18.5957H48.3342C46.7689 18.5957 45.478 19.8866 45.478 21.4519V40.7353H52.5136Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M45.478 50.0293V53.6601C45.478 55.2253 46.7689 56.5163 48.3342 56.5163H49.6735C51.2388 56.5163 52.5298 55.2253 52.5298 53.6601V50.0293H45.478Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M28.6957 41.8652H25.065C23.4997 41.8652 22.2087 43.14 22.2087 44.7214V46.0608C22.2087 47.6261 23.4836 48.917 25.065 48.917H28.6957V41.8652Z"
|
||||
fill="#95DE2D" />
|
||||
<path
|
||||
d="M57.2738 41.8652H37.9903V48.917H57.2738C58.839 48.917 60.13 47.6261 60.13 46.0608V44.7214C60.13 43.14 58.839 41.8652 57.2738 41.8652Z"
|
||||
fill="#95DE2D" />
|
||||
<path
|
||||
d="M44.3484 26.2129H25.065C23.4997 26.2129 22.2087 27.4877 22.2087 29.0691V30.4084C22.2087 31.9737 23.4836 33.2647 25.065 33.2647H44.3484V26.2129Z"
|
||||
fill="#95DE2D" />
|
||||
<path
|
||||
d="M57.2738 26.2128H53.6431V33.2646H57.2738C58.8391 33.2646 60.1301 31.9898 60.1301 30.4084V29.069C60.1301 27.4876 58.8391 26.2128 57.2738 26.2128Z"
|
||||
fill="#95DE2D" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M108.864 99.4959H129.086C129.852 99.4959 130.587 99.8008 131.129 100.343C131.671 100.886 131.975 101.622 131.975 102.39L131.975 127.191C131.975 128.296 130.688 128.899 129.841 128.191L124.6 123.804H108.864C108.098 123.804 107.363 123.499 106.821 122.956C106.279 122.413 105.975 121.677 105.975 120.91V102.39C105.975 101.622 106.279 100.886 106.821 100.343C107.363 99.8008 108.098 99.4959 108.864 99.4959ZM116.549 112.318H110.597V104.126H127.353V112.318H121.499V115.152H123.64C124.026 115.152 124.22 115.619 123.947 115.893L119.331 120.517C119.161 120.686 118.887 120.686 118.718 120.517L114.102 115.893C113.829 115.619 114.022 115.152 114.408 115.152H116.549V112.318Z"
|
||||
fill="#95DE2D" />
|
||||
<path
|
||||
d="M190.408 151.152V162.299C190.408 163.204 191.145 163.95 192.059 163.95H192.833C193.738 163.95 194.484 163.204 194.484 162.299V151.152H190.408Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M194.475 145.779V143.68C194.475 142.775 193.738 142.029 192.833 142.029H192.059C191.154 142.029 190.408 142.775 190.408 143.68V145.779H194.475Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M203.523 154.827V143.68C203.523 142.775 202.786 142.029 201.881 142.029H201.107C200.202 142.029 199.456 142.775 199.456 143.68V154.827H203.523Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M199.456 160.201V162.3C199.456 163.204 200.202 163.951 201.107 163.951H201.881C202.786 163.951 203.533 163.204 203.533 162.3V160.201H199.456Z"
|
||||
fill="#82C91E" />
|
||||
<path
|
||||
d="M189.755 155.48H187.656C186.751 155.48 186.005 156.217 186.005 157.131V157.905C186.005 158.81 186.742 159.556 187.656 159.556H189.755V155.48Z"
|
||||
fill="#95DE2D" />
|
||||
<path
|
||||
d="M206.275 155.48H195.128V159.556H206.275C207.18 159.556 207.926 158.81 207.926 157.905V157.131C207.926 156.217 207.18 155.48 206.275 155.48Z"
|
||||
fill="#95DE2D" />
|
||||
<path
|
||||
d="M198.803 146.433H187.656C186.751 146.433 186.005 147.17 186.005 148.084V148.859C186.005 149.763 186.742 150.51 187.656 150.51H198.803V146.433Z"
|
||||
fill="#95DE2D" />
|
||||
<path
|
||||
d="M206.275 146.433H204.176V150.51H206.275C207.18 150.51 207.926 149.773 207.926 148.859V148.084C207.926 147.17 207.18 146.433 206.275 146.433Z"
|
||||
fill="#95DE2D" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 21 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user