mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
286 Commits
checkstyle
...
beta-1.4.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7536f16c61 | ||
|
|
ab628c1921 | ||
|
|
85e53479f2 | ||
|
|
116ee97056 | ||
|
|
78938f1ac6 | ||
|
|
afff66eaff | ||
|
|
a8624cd507 | ||
|
|
e7fc37d81e | ||
|
|
7bd220f18d | ||
|
|
dea05c85a2 | ||
|
|
174ca3cfb8 | ||
|
|
961af66c8e | ||
|
|
a86ea454d0 | ||
|
|
a7877bf7ee | ||
|
|
62ae0f745b | ||
|
|
f83abbe63d | ||
|
|
e0b6b8435d | ||
|
|
d3c7832245 | ||
|
|
a043e8b1cf | ||
|
|
bc013296f6 | ||
|
|
c1fabcd46b | ||
|
|
3c08e86822 | ||
|
|
de2c9670d5 | ||
|
|
9632754274 | ||
|
|
b275a0ffff | ||
|
|
74a3f54d28 | ||
|
|
edcb234b93 | ||
|
|
dae00c7e4e | ||
|
|
29b16c4d74 | ||
|
|
40d58a9359 | ||
|
|
60a1a4d2d1 | ||
|
|
238aeb3abd | ||
|
|
62c16fad09 | ||
|
|
68e57bda0d | ||
|
|
0df73dbf0a | ||
|
|
5b648cbd35 | ||
|
|
5e7891d78a | ||
|
|
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 | ||
|
|
1d04bbcb4f | ||
|
|
43b0d1d543 | ||
|
|
1bfd9e4eb0 | ||
|
|
2295db4361 | ||
|
|
8fca06e040 | ||
|
|
3f7c9af3a9 | ||
|
|
93178d2f28 | ||
|
|
2755d3f470 | ||
|
|
7efc3ca78f | ||
|
|
d6767a62b9 | ||
|
|
2821460648 | ||
|
|
7aa1073bf5 | ||
|
|
3ff7349b40 | ||
|
|
22593722a7 | ||
|
|
e91ad962cb | ||
|
|
53d9a9b43b | ||
|
|
6c702bad0a | ||
|
|
0dc2aba22f | ||
|
|
5a8b822e08 | ||
|
|
8ac6b0155b | ||
|
|
372810f48e | ||
|
|
1b4ab4f945 | ||
|
|
407ddad0a8 | ||
|
|
77a986318e | ||
|
|
1809be4656 | ||
|
|
6d1a0a5792 | ||
|
|
85dc27ed77 | ||
|
|
3f8df34f5c | ||
|
|
eb08781460 | ||
|
|
e79abeff2e | ||
|
|
d3dbcfd62d | ||
|
|
c4c70f5ac2 |
@@ -32,8 +32,9 @@ test:
|
|||||||
extends: .base-test
|
extends: .base-test
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
- ./gradlew -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 assembleOfficialDebug :briar-headless:linuxJars
|
||||||
|
- ./gradlew -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
when: always
|
when: always
|
||||||
@@ -61,7 +62,7 @@ android test:
|
|||||||
when: on_failure
|
when: on_failure
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
when: on_success
|
when: manual
|
||||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
changes:
|
changes:
|
||||||
- briar-android/**/*
|
- briar-android/**/*
|
||||||
@@ -84,35 +85,44 @@ test_reproducible:
|
|||||||
|
|
||||||
.optional_tests:
|
.optional_tests:
|
||||||
stage: optional_tests
|
stage: optional_tests
|
||||||
before_script:
|
extends: .base-test
|
||||||
- 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/
|
|
||||||
|
|
||||||
bridge test:
|
bridge test:
|
||||||
extends: .optional_tests
|
extends: .optional_tests
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
when: on_success
|
when: on_success
|
||||||
allow_failure: true
|
allow_failure: false
|
||||||
- if: '$CI_COMMIT_TAG == null'
|
- if: '$CI_COMMIT_TAG == null'
|
||||||
when: manual
|
when: manual
|
||||||
allow_failure: true
|
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:
|
pre_release_tests:
|
||||||
extends: .optional_tests
|
extends: .optional_tests
|
||||||
|
script:
|
||||||
|
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
|
||||||
|
timeout: 3h
|
||||||
only:
|
only:
|
||||||
- tags
|
- tags
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ our site.
|
|||||||
|
|
||||||
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
|
[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
|
## Donate
|
||||||
|
|
||||||
[](https://liberapay.com/Briar/donate) [](https://flattr.com/t/592836/)
|
[](https://liberapay.com/Briar/donate) [](https://flattr.com/t/592836/)
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10401
|
versionCode 10407
|
||||||
versionName "1.4.1"
|
versionName "1.4.7"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@@ -42,8 +42,8 @@ configurations {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':bramble-core', configuration: 'default')
|
implementation project(path: ':bramble-core', configuration: 'default')
|
||||||
tor 'org.briarproject:tor-android:0.3.5.15'
|
tor "org.briarproject:tor-android:$tor_version"
|
||||||
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
|
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
|
||||||
|
|
||||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ dependencies {
|
|||||||
testImplementation "junit:junit:$junit_version"
|
testImplementation "junit:junit:$junit_version"
|
||||||
testImplementation "org.jmock:jmock:$jmock_version"
|
testImplementation "org.jmock:jmock:$jmock_version"
|
||||||
testImplementation "org.jmock:jmock-junit4:$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'
|
def torBinariesDir = 'src/main/res/raw'
|
||||||
@@ -70,11 +70,6 @@ clean.dependsOn cleanTorBinaries
|
|||||||
|
|
||||||
task unpackTorBinaries {
|
task unpackTorBinaries {
|
||||||
doLast {
|
doLast {
|
||||||
copy {
|
|
||||||
from configurations.tor.collect { zipTree(it) }
|
|
||||||
into torBinariesDir
|
|
||||||
include 'geoip.zip'
|
|
||||||
}
|
|
||||||
configurations.tor.each { outer ->
|
configurations.tor.each { outer ->
|
||||||
zipTree(outer).each { inner ->
|
zipTree(outer).each { inner ->
|
||||||
if (inner.name.endsWith('_arm_pie.zip')) {
|
if (inner.name.endsWith('_arm_pie.zip')) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
-keep,includedescriptorclasses class org.briarproject.** { *; }
|
# Keep the H2 classes that are loaded via reflection
|
||||||
|
-keep class org.h2.Driver { *; }
|
||||||
-keep class org.h2.** { *; }
|
-keep class org.h2.engine.Engine { *; }
|
||||||
|
-keep class org.h2.store.fs.** { *; }
|
||||||
|
# Don't warn about unused dependencies of H2 classes
|
||||||
-dontwarn org.h2.**
|
-dontwarn org.h2.**
|
||||||
-dontnote org.h2.**
|
-dontnote org.h2.**
|
||||||
|
|
||||||
@@ -15,5 +17,4 @@
|
|||||||
-dontwarn sun.misc.Unsafe
|
-dontwarn sun.misc.Unsafe
|
||||||
-dontnote com.google.common.**
|
-dontnote com.google.common.**
|
||||||
|
|
||||||
# UPnP library isn't used
|
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl
|
||||||
-dontwarn org.bitlet.weupnp.**
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import android.net.LinkAddress;
|
|||||||
import android.net.LinkProperties;
|
import android.net.LinkProperties;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
|
import android.net.wifi.WifiInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventExecutor;
|
import org.briarproject.bramble.api.event.EventExecutor;
|
||||||
@@ -38,6 +40,7 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||||
import static android.content.Intent.ACTION_SCREEN_ON;
|
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
@@ -111,15 +114,37 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NetworkStatus getNetworkStatus() {
|
public NetworkStatus getNetworkStatus() {
|
||||||
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
// https://issuetracker.google.com/issues/175055271
|
||||||
boolean connected = net != null && net.isConnected();
|
try {
|
||||||
boolean wifi = false, ipv6Only = false;
|
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
|
||||||
if (connected) {
|
boolean connected = net != null && net.isConnected();
|
||||||
wifi = net.getType() == TYPE_WIFI;
|
boolean wifi = false, ipv6Only = false;
|
||||||
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
|
if (connected) {
|
||||||
else ipv6Only = areAllAvailableNetworksIpv6Only();
|
wifi = net.getType() == TYPE_WIFI;
|
||||||
|
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
|
||||||
|
else ipv6Only = areAllAvailableNetworksIpv6Only();
|
||||||
|
}
|
||||||
|
return new NetworkStatus(connected, wifi, ipv6Only);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// Without the ConnectivityManager we can't detect whether we have
|
||||||
|
// internet access. Assume we do, which is probably less harmful
|
||||||
|
// than assuming we don't. Likewise, assume the connection is
|
||||||
|
// IPv6-only. Fall back to the WifiManager to detect whether we
|
||||||
|
// have a wifi connection.
|
||||||
|
LOG.info("ConnectivityManager is broken, guessing connectivity");
|
||||||
|
boolean connected = true, wifi = false, ipv6Only = true;
|
||||||
|
WifiManager wm = (WifiManager) app.getSystemService(WIFI_SERVICE);
|
||||||
|
if (wm != null) {
|
||||||
|
WifiInfo info = wm.getConnectionInfo();
|
||||||
|
if (info != null && info.getIpAddress() != 0) {
|
||||||
|
LOG.info("Connected to wifi");
|
||||||
|
wifi = true;
|
||||||
|
ipv6Only = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new NetworkStatus(connected, wifi, ipv6Only);
|
||||||
}
|
}
|
||||||
return new NetworkStatus(connected, wifi, ipv6Only);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,23 +155,29 @@ class AndroidNetworkManager implements NetworkManager, Service {
|
|||||||
*/
|
*/
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
private boolean isActiveNetworkIpv6Only() {
|
private boolean isActiveNetworkIpv6Only() {
|
||||||
Network net = connectivityManager.getActiveNetwork();
|
// https://issuetracker.google.com/issues/175055271
|
||||||
if (net == null) {
|
try {
|
||||||
LOG.info("No active network");
|
Network net = connectivityManager.getActiveNetwork();
|
||||||
|
if (net == null) {
|
||||||
|
LOG.info("No active network");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LinkProperties props = connectivityManager.getLinkProperties(net);
|
||||||
|
if (props == null) {
|
||||||
|
LOG.info("No link properties for active network");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean hasIpv6Unicast = false;
|
||||||
|
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
||||||
|
InetAddress addr = linkAddress.getAddress();
|
||||||
|
if (addr instanceof Inet4Address) return false;
|
||||||
|
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
|
||||||
|
}
|
||||||
|
return hasIpv6Unicast;
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
LinkProperties props = connectivityManager.getLinkProperties(net);
|
|
||||||
if (props == null) {
|
|
||||||
LOG.info("No link properties for active network");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
boolean hasIpv6Unicast = false;
|
|
||||||
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
|
||||||
InetAddress addr = linkAddress.getAddress();
|
|
||||||
if (addr instanceof Inet4Address) return false;
|
|
||||||
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
|
|
||||||
}
|
|
||||||
return hasIpv6Unicast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,13 +32,22 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
|||||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||||
String uri = p.get(PROP_URI);
|
String uri = p.get(PROP_URI);
|
||||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||||
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
try {
|
||||||
|
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||||
String uri = p.get(PROP_URI);
|
String uri = p.get(PROP_URI);
|
||||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||||
return app.getContentResolver().openOutputStream(Uri.parse(uri));
|
try {
|
||||||
|
return app.getContentResolver()
|
||||||
|
.openOutputStream(Uri.parse(uri), "wt");
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,16 +175,24 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
@Nullable
|
@Nullable
|
||||||
private InetAddress getWifiClientIpv6Address() {
|
private InetAddress getWifiClientIpv6Address() {
|
||||||
for (Network net : connectivityManager.getAllNetworks()) {
|
// https://issuetracker.google.com/issues/175055271
|
||||||
NetworkCapabilities caps =
|
try {
|
||||||
connectivityManager.getNetworkCapabilities(net);
|
for (Network net : connectivityManager.getAllNetworks()) {
|
||||||
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
|
NetworkCapabilities caps =
|
||||||
LinkProperties props = connectivityManager.getLinkProperties(net);
|
connectivityManager.getNetworkCapabilities(net);
|
||||||
if (props == null) continue;
|
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) {
|
||||||
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
continue;
|
||||||
InetAddress addr = linkAddress.getAddress();
|
}
|
||||||
if (isIpv6LinkLocalAddress(addr)) return addr;
|
LinkProperties props =
|
||||||
|
connectivityManager.getLinkProperties(net);
|
||||||
|
if (props == null) continue;
|
||||||
|
for (LinkAddress linkAddress : props.getLinkAddresses()) {
|
||||||
|
InetAddress addr = linkAddress.getAddress();
|
||||||
|
if (isIpv6LinkLocalAddress(addr)) return addr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -227,12 +235,17 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
// network's socket factory may try to connect via another network
|
// network's socket factory may try to connect via another network
|
||||||
private SocketFactory getSocketFactory() {
|
private SocketFactory getSocketFactory() {
|
||||||
if (SDK_INT < 21) return SocketFactory.getDefault();
|
if (SDK_INT < 21) return SocketFactory.getDefault();
|
||||||
for (Network net : connectivityManager.getAllNetworks()) {
|
// https://issuetracker.google.com/issues/175055271
|
||||||
NetworkCapabilities caps =
|
try {
|
||||||
connectivityManager.getNetworkCapabilities(net);
|
for (Network net : connectivityManager.getAllNetworks()) {
|
||||||
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
|
NetworkCapabilities caps =
|
||||||
return net.getSocketFactory();
|
connectivityManager.getNetworkCapabilities(net);
|
||||||
|
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
|
||||||
|
return net.getSocketFactory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
LOG.warning("Could not find suitable socket factory");
|
LOG.warning("Could not find suitable socket factory");
|
||||||
return SocketFactory.getDefault();
|
return SocketFactory.getDefault();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
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.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.network.NetworkManager;
|
import org.briarproject.bramble.api.network.NetworkManager;
|
||||||
@@ -60,6 +61,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final File torDirectory;
|
private final File torDirectory;
|
||||||
private int torSocksPort;
|
private int torSocksPort;
|
||||||
private int torControlPort;
|
private int torControlPort;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@@ -77,7 +79,8 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
Clock clock,
|
Clock clock,
|
||||||
@TorDirectory File torDirectory,
|
@TorDirectory File torDirectory,
|
||||||
@TorSocksPort int torSocksPort,
|
@TorSocksPort int torSocksPort,
|
||||||
@TorControlPort int torControlPort) {
|
@TorControlPort int torControlPort,
|
||||||
|
CryptoComponent crypto) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
@@ -94,6 +97,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
this.torDirectory = torDirectory;
|
this.torDirectory = torDirectory;
|
||||||
this.torSocksPort = torSocksPort;
|
this.torSocksPort = torSocksPort;
|
||||||
this.torControlPort = torControlPort;
|
this.torControlPort = torControlPort;
|
||||||
|
this.crypto = crypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,7 +139,8 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
|
TorRendezvousCrypto torRendezvousCrypto =
|
||||||
|
new TorRendezvousCryptoImpl(crypto);
|
||||||
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
|
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
|
||||||
wakefulIoExecutor, app, networkManager, locationUtils,
|
wakefulIoExecutor, app, networkManager, locationUtils,
|
||||||
torSocketFactory, clock, resourceProvider,
|
torSocketFactory, clock, resourceProvider,
|
||||||
|
|||||||
@@ -116,10 +116,12 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
long dueMillis = now + MILLISECONDS.convert(delay, unit);
|
long dueMillis = now + MILLISECONDS.convert(delay, unit);
|
||||||
Runnable wakeful = () ->
|
Runnable wakeful = () ->
|
||||||
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
|
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
|
||||||
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
// Acquire the lock before scheduling the check to ensure the check
|
||||||
ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
|
// doesn't access the task queue before the task has been added
|
||||||
cancelled);
|
ScheduledTask s;
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
Future<?> check = scheduleCheckForDueTasks(delay, unit);
|
||||||
|
s = new ScheduledTask(wakeful, dueMillis, check, cancelled);
|
||||||
tasks.add(s);
|
tasks.add(s);
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
@@ -136,6 +138,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
return schedule(wrapped, executor, delay, unit, cancelled);
|
return schedule(wrapped, executor, delay, unit, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
|
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
|
||||||
Runnable wakeful = () -> wakeLockManager.runWakefully(
|
Runnable wakeful = () -> wakeLockManager.runWakefully(
|
||||||
this::runDueTasks, "TaskScheduler");
|
this::runDueTasks, "TaskScheduler");
|
||||||
@@ -206,7 +209,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
|
|||||||
private final Future<?> check;
|
private final Future<?> check;
|
||||||
private final AtomicBoolean cancelled;
|
private final AtomicBoolean cancelled;
|
||||||
|
|
||||||
public ScheduledTask(Runnable task, long dueMillis,
|
private ScheduledTask(Runnable task, long dueMillis,
|
||||||
Future<?> check, AtomicBoolean cancelled) {
|
Future<?> check, AtomicBoolean cancelled) {
|
||||||
this.task = task;
|
this.task = task;
|
||||||
this.dueMillis = dueMillis;
|
this.dueMillis = dueMillis;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Looper;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
@@ -134,4 +135,8 @@ public class AndroidUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isUiThread() {
|
||||||
|
return Looper.myLooper() == Looper.getMainLooper();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -44,7 +44,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
|
|||||||
private AndroidAccountManager accountManager;
|
private AndroidAccountManager accountManager;
|
||||||
|
|
||||||
public AndroidAccountManagerTest() {
|
public AndroidAccountManagerTest() {
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||||
app = context.mock(Application.class);
|
app = context.mock(Application.class);
|
||||||
applicationInfo = new ApplicationInfo();
|
applicationInfo = new ApplicationInfo();
|
||||||
applicationInfo.dataDir = testDir.getAbsolutePath();
|
applicationInfo.dataDir = testDir.getAbsolutePath();
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
dependencyVerification {
|
dependencyVerification {
|
||||||
verify = [
|
verify = [
|
||||||
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
|
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
|
||||||
'com.android.tools.analytics-library:protos:27.1.3:protos-27.1.3.jar:0d9e6cff60b318baac250b6f5bb076a8161103338bf2749cdf1db8a5a13a1f12',
|
'com.android.tools.analytics-library:protos:30.0.3:protos-30.0.3.jar:f62b89dcd9de719c6a7b7e15fb1dd20e45b57222e675cf633607bd0ed6bca7e7',
|
||||||
'com.android.tools.analytics-library:shared:27.1.3:shared-27.1.3.jar:10d2a51d8f89ff4ac849888e5a9c60b10e879c30d78545ec1da4d3df7bd56ae4',
|
'com.android.tools.analytics-library:shared:30.0.3:shared-30.0.3.jar:05aa9ba3cc890354108521fdf99802565aae5dd6ca44a6ac8bb8d594d1c1cd15',
|
||||||
'com.android.tools.analytics-library:tracker:27.1.3:tracker-27.1.3.jar:589b355a2ba796cbc0a2b2295737de6661f078262e5f87cd6f540b8d011e5ebb',
|
'com.android.tools.analytics-library:tracker:30.0.3:tracker-30.0.3.jar:5d0ef35bf6733e96210b5085a2a202152921bf834d345959dce1ca3369b528df',
|
||||||
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
|
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
|
||||||
'com.android.tools.build:apksig:4.1.3:apksig-4.1.3.jar:a851980c678ff7a6785388b9a9e8cc094788ce3c4a985ad2b19c2028fd3c631a',
|
'com.android.tools.build:apksig:7.0.3:apksig-7.0.3.jar:012337a2803c9a30dfc41dcbc6450686ee9e5f582549f7f126479f743a343ec9',
|
||||||
'com.android.tools.build:apkzlib:4.1.3:apkzlib-4.1.3.jar:475903065e7e83a8c1ba78d267c97a54dc5a04d768b535093850423d7b11f2c8',
|
'com.android.tools.build:apkzlib:7.0.3:apkzlib-7.0.3.jar:b31e53174c92db83c5cc6e7dac6734ea4e907a72e452c2bf1818dfd082c59397',
|
||||||
'com.android.tools.build:builder-model:4.1.3:builder-model-4.1.3.jar:2624a1436c3ab39dd91d3ecf9409a594b0f89ea5cab255f2e9ff11f5ee03d274',
|
'com.android.tools.build:builder-model:7.0.3:builder-model-7.0.3.jar:483f99d7494a5bed027e1e8d29111384cf535d4842f0be5a79805bd44bb68d4e',
|
||||||
'com.android.tools.build:builder-test-api:4.1.3:builder-test-api-4.1.3.jar:3d2af66726b06b53b8d6d497efcee39ff9f77eb2f8d2cce38b31502383a40d2c',
|
'com.android.tools.build:builder-test-api:7.0.3:builder-test-api-7.0.3.jar:f6de4bc2cef545e8367bf82d7c733829c7be3b0b3b8b09fd8c58f2150e59ab46',
|
||||||
'com.android.tools.build:builder:4.1.3:builder-4.1.3.jar:a40426cd6d68f6a722ef4950058c075e4547025e8c2fd78e732ad89f15176f84',
|
'com.android.tools.build:builder:7.0.3:builder-7.0.3.jar:c6952da0094b094c2ba0fe84c675622097c5d9b9f9beb53485b860320540cf1d',
|
||||||
'com.android.tools.build:gradle-api:4.1.3:gradle-api-4.1.3.jar:11b1fb9de658bdcf9290b1c1517060d0c4d93f2b27975934989ca4ac890bc077',
|
'com.android.tools.build:manifest-merger:30.0.3:manifest-merger-30.0.3.jar:72b346ba6318b4b6260e6e49df4bea5da2e12329ab6c2beb2269c49a9f51f178',
|
||||||
'com.android.tools.build:manifest-merger:27.1.3:manifest-merger-27.1.3.jar:ce8d4009b1f1584777a7ffa1da3b0551dc316bc8e08112e442c352af70f46f2d',
|
'com.android.tools.ddms:ddmlib:30.0.3:ddmlib-30.0.3.jar:7a914a68ab93393657297234e2f37b22410ae9a433cba692ce8c727c9607e3bb',
|
||||||
'com.android.tools.ddms:ddmlib:27.1.3:ddmlib-27.1.3.jar:8f76e8236d2b9eebf26378746dad025c4c7c056a02e133dae4ddef47b283c710',
|
'com.android.tools.external.com-intellij:intellij-core:30.0.3:intellij-core-30.0.3.jar:1ebe858d3f58eeaa8c06507f8ac0f1c7051e6c61f35a70f3c3967d5734d3abc5',
|
||||||
'com.android.tools.external.com-intellij:intellij-core:27.1.3:intellij-core-27.1.3.jar:652814fa099b4746fb6f10e19718e476952e8b5bac24e17d914f90650ad21808',
|
'com.android.tools.external.com-intellij:kotlin-compiler:30.0.3:kotlin-compiler-30.0.3.jar:ed00e441f427cb4e0d418287b9da30b12b7f735f9af32e6b5d3dc960b6a742fc',
|
||||||
'com.android.tools.external.com-intellij:kotlin-compiler:27.1.3:kotlin-compiler-27.1.3.jar:8d7a78d5efd213c5e467e42bd205582aad73ffc77ee5dc18eb1361c9af72f125',
|
'com.android.tools.external.org-jetbrains:uast:30.0.3:uast-30.0.3.jar:a77801bee6ff509910e459525c9c34d7f04b066ade123547f16f1917548eadea',
|
||||||
'com.android.tools.external.org-jetbrains:uast:27.1.3:uast-27.1.3.jar:aea53944a1ac6a05f12297b55290e8cbecfe54c4166260cfba4405823bfe1c78',
|
'com.android.tools.layoutlib:layoutlib-api:30.0.3:layoutlib-api-30.0.3.jar:4caa87e9ca2e11315f650d576cd59fec1793373bc3fca3f6d53c029e7534e7c4',
|
||||||
'com.android.tools.layoutlib:layoutlib-api:27.1.3:layoutlib-api-27.1.3.jar:23875ce0a8429f33a4e86cc358f658faa0ba9c576f5f05760e544b453d67d04b',
|
'com.android.tools.lint:lint-api:30.0.3:lint-api-30.0.3.jar:bcecbd2f752a6560096a9029a47d1de6bd788a51bab505c5ebfba6a18524b983',
|
||||||
'com.android.tools.lint:lint-api:27.1.3:lint-api-27.1.3.jar:97666be32bcadacd944416ea334a9575ef8f4ad0c8f333151491ff4a7df43e1c',
|
'com.android.tools.lint:lint-checks:30.0.3:lint-checks-30.0.3.jar:25a7cd42dc3ad502337f131fb8b7e873c53301db0a67b1c64dd4ae7a8eb66cec',
|
||||||
'com.android.tools.lint:lint-checks:27.1.3:lint-checks-27.1.3.jar:b2d71ae84a31490fe9ff26c706163fe245b2aea98e3eb747214c1085df444708',
|
'com.android.tools.lint:lint-gradle:30.0.3:lint-gradle-30.0.3.jar:94544d6147a809bf2fd3440e51f28a4e42e547d74aab53eefd74938cdad42c26',
|
||||||
'com.android.tools.lint:lint-gradle-api:27.1.3:lint-gradle-api-27.1.3.jar:e54131c287a2954e6ed78a3351e5e10e35a1da2f09ac443bf44b705c71b63a4d',
|
'com.android.tools.lint:lint-model:30.0.3:lint-model-30.0.3.jar:0b940a7f575c2ff5cbd038260f41dde686a93c672213881ead3ce8af3513b396',
|
||||||
'com.android.tools.lint:lint-gradle:27.1.3:lint-gradle-27.1.3.jar:6a79e48943649d63665db7b17dbaff7af93e94ab9b15072f1a4d90486294ee9f',
|
'com.android.tools.lint:lint:30.0.3:lint-30.0.3.jar:ee4f11001e0c7e3b776e0d67399ad354b19b0f168822ec2b7db47c0910ed227d',
|
||||||
'com.android.tools.lint:lint-model:27.1.3:lint-model-27.1.3.jar:acb9e792db7000e38e3c3ca21a9b14f2de6549d7a3fc92a97ffba3d06345e5bf',
|
'com.android.tools:annotations:30.0.3:annotations-30.0.3.jar:5c1944982fda8555855c4f5422fabf0dc8e2306e1f5460e9ad82dae71316bc31',
|
||||||
'com.android.tools.lint:lint:27.1.3:lint-27.1.3.jar:5a2e69d0901a3a476a5b2d5001de755868113145f5f6aa557750cfad5389a44b',
|
'com.android.tools:common:30.0.3:common-30.0.3.jar:8751efaaf2c2ddd1f0a37526c794347def6a3057ca9fc510307c13a6cf0d036f',
|
||||||
'com.android.tools:annotations:27.1.3:annotations-27.1.3.jar:904dd771883496d5dfc86619ab2555968ea4e8a29d7a5f4f7cae6fbf5429f8f5',
|
'com.android.tools:dvlib:30.0.3:dvlib-30.0.3.jar:5affafcec390041e5afd64cb924153f5e474db47ee8ccc2f555b495083141233',
|
||||||
'com.android.tools:common:27.1.3:common-27.1.3.jar:17ab4728e3ea50f047dd5937f0faf35f2c5416962ed74891057087ddc328bf96',
|
'com.android.tools:repository:30.0.3:repository-30.0.3.jar:0a40c6f16c506903ce2c609affd8228aceda73a69d93dfa42d4f02b8491449f6',
|
||||||
'com.android.tools:dvlib:27.1.3:dvlib-27.1.3.jar:cead1c0c356cbe43e6855b0330fe09ef4bec2c72e22bdb4c6e7cf7e6b1dfbc37',
|
'com.android.tools:sdk-common:30.0.3:sdk-common-30.0.3.jar:b45570a380360236ffee0f6bb593d66b673bad3834dfe0d6c9871fa7188ee0eb',
|
||||||
'com.android.tools:repository:27.1.3:repository-27.1.3.jar:99de1a178855b56b8cd91a56296f1e0a9399c445e6acc51f1d2927947cc472cb',
|
'com.android.tools:sdklib:30.0.3:sdklib-30.0.3.jar:7088f20a414fab170a21e457825e14ebe099f753558e02c8acc12c67eb412162',
|
||||||
'com.android.tools:sdk-common:27.1.3:sdk-common-27.1.3.jar:b591e2aa0f1be600795f5c9e2bf81cba9b052bee452fc86c3362b5dd9e427a14',
|
'com.android:signflinger:7.0.3:signflinger-7.0.3.jar:903a4536db3e96b4e1e1dc1e400eb0b91bf7866d9b39cd7ec94d75dde158f152',
|
||||||
'com.android.tools:sdklib:27.1.3:sdklib-27.1.3.jar:ad6c08a45fe2904d05656bdddf9f623fa5c1d16bbd7b8d6a270a0734136ae02e',
|
'com.android:zipflinger:7.0.3:zipflinger-7.0.3.jar:fd209c960a3eff7a339e6fcba07d5e9ef4604d1633c69ab2df987460d9804140',
|
||||||
'com.android:signflinger:4.1.3:signflinger-4.1.3.jar:f3103b55ccdc8dd9ee2517eb26af93b904d41303726594372d0df59d51156e5c',
|
'com.beust:jcommander:1.78:jcommander-1.78.jar:7891debb84b5f83e9bd57593ebece3399abbe0fd938cf306b3534c57913b9615',
|
||||||
'com.android:zipflinger:4.1.3:zipflinger-4.1.3.jar:48569896c0497268308a8014c66eb0f2bace2b9e2fc9390f3012823fb86387d5',
|
'com.github.javaparser:javaparser-core:3.17.0:javaparser-core-3.17.0.jar:23f5c982e1c7771423d37d52c774e8d2e80fd7ea7305ebe448797a96f67e6fca',
|
||||||
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
|
'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.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||||
'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
|
'com.google.code.gson:gson:2.8.6:gson-2.8.6.jar:c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f',
|
||||||
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
|
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
|
||||||
'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8',
|
'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8',
|
||||||
'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4',
|
'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4',
|
||||||
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
|
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
|
||||||
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
|
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
|
||||||
'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d',
|
'com.google.errorprone:error_prone_annotations:2.3.4:error_prone_annotations-2.3.4.jar:baf7d6ea97ce606c53e11b6854ba5f2ce7ef5c24dddf0afa18d1260bd25b002c',
|
||||||
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
|
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
|
||||||
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
|
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
|
||||||
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
|
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
|
||||||
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
|
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
|
||||||
'com.google.guava:guava:28.1-jre:guava-28.1-jre.jar:30beb8b8527bd07c6e747e77f1a92122c2f29d57ce347461a4a55eb26e382da4',
|
'com.google.guava:guava:30.1-jre:guava-30.1-jre.jar:e6dd072f9d3fe02a4600688380bd422bdac184caf6fe2418cfdd0934f09432aa',
|
||||||
'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.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.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
|
||||||
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
|
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
|
||||||
@@ -54,63 +54,108 @@ dependencyVerification {
|
|||||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||||
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
|
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
|
||||||
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
|
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
|
||||||
'com.sun.istack:istack-commons-runtime:3.0.7:istack-commons-runtime-3.0.7.jar:6443e10ba2e259fb821d9b6becf10db5316285fc30c53cec9d7b19a3877e7fdf',
|
'com.sun.istack:istack-commons-runtime:3.0.8:istack-commons-runtime-3.0.8.jar:4ffabb06be454a05e4398e20c77fa2b6308d4b88dfbef7ca30a76b5b7d5505ef',
|
||||||
'com.sun.xml.fastinfoset:FastInfoset:1.2.15:FastInfoset-1.2.15.jar:785861db11ca1bd0d1956682b974ad73eb19cd3e01a4b3fa82d62eca97210aec',
|
'com.sun.xml.fastinfoset:FastInfoset:1.2.16:FastInfoset-1.2.16.jar:056f3a1e144409f21ed16afc26805f58e9a21f3fce1543c42d400719d250c511',
|
||||||
|
'com.thoughtworks.qdox:qdox:1.12.1:qdox-1.12.1.jar:21fba22f830e9268f07cf4ab2d99e8181abbdcb0cb91ee0228eb3cb918dcdd1d',
|
||||||
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
|
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
|
||||||
|
'commons-io:commons-io:2.4:commons-io-2.4.jar:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581',
|
||||||
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
|
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
|
||||||
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
|
'info.picocli:picocli:4.5.2:picocli-4.5.2.jar:b4395e9a67932616efd2245d984bf5fcd453c2c5049558c3ce959ac2af4d3fac',
|
||||||
'javax.activation:javax.activation-api:1.2.0:javax.activation-api-1.2.0.jar:43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393',
|
'it.unimi.dsi:fastutil:8.4.0:fastutil-8.4.0.jar:2ad2824a4a0a0eb836b52ee2fc84ba2134f44bce7bfa54015ae3f31c710a3071',
|
||||||
|
'jakarta.activation:jakarta.activation-api:1.2.1:jakarta.activation-api-1.2.1.jar:8b0a0f52fa8b05c5431921a063ed866efaa41dadf2e3a7ee3e1961f2b0d9645b',
|
||||||
|
'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2:jakarta.xml.bind-api-2.3.2.jar:69156304079bdeed9fc0ae3b39389f19b3cc4ba4443bc80508995394ead742ea',
|
||||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
|
'jline:jline:2.14.6:jline-2.14.6.jar:97d1acaac82409be42e622d7a54d3ae9d08517e8aefdea3d2ba9791150c2f02d',
|
||||||
|
'junit:junit:4.13.1:junit-4.13.1.jar:c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122',
|
||||||
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
|
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
|
||||||
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
|
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
|
||||||
|
'net.java.dev.jna:jna-platform:5.6.0:jna-platform-5.6.0.jar:9ecea8bf2b1b39963939d18b70464eef60c508fed8820f9dcaba0c35518eabf7',
|
||||||
|
'net.java.dev.jna:jna:5.6.0:jna-5.6.0.jar:5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf',
|
||||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||||
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
|
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
|
||||||
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
|
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
|
||||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||||
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
|
'org.apache.ant:ant-antlr:1.10.9:ant-antlr-1.10.9.jar:7623dc9d0f20ea713290c6bf1a23f4c059447aef7ff9f5b2be75960f3f028d2e',
|
||||||
|
'org.apache.ant:ant-junit:1.10.9:ant-junit-1.10.9.jar:960bdc8827954d62206ba42d0a68a7ee4476175ba47bb113e17e77cce7394630',
|
||||||
|
'org.apache.ant:ant-launcher:1.10.9:ant-launcher-1.10.9.jar:fcce891f57f3be72149ff96ac2a80574165b3e0839866b95d24528f3027d50c1',
|
||||||
|
'org.apache.ant:ant:1.10.9:ant-1.10.9.jar:0715478af585ea80a18985613ebecdc7922122d45b2c3c970ff9b352cddb75fc',
|
||||||
|
'org.apache.commons:commons-compress:1.20:commons-compress-1.20.jar:0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e',
|
||||||
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
|
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
|
||||||
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
|
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
|
||||||
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
|
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
|
||||||
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
|
||||||
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
|
||||||
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
|
'org.briarproject:obfs4proxy-android:0.0.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89',
|
||||||
'org.briarproject:tor-android:0.3.5.15:tor-android-0.3.5.15.jar:560c5070166300b396cb2f28d82d9f639ee1fb5479096a3cef67da56d39937ad',
|
'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-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||||
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
|
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
|
||||||
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
|
'org.codehaus.groovy:groovy-ant:3.0.7:groovy-ant-3.0.7.jar:6ed2ba82813d128f7050c24142e87b3dc2ad8b504786280eb03e81f0cf6a5793',
|
||||||
|
'org.codehaus.groovy:groovy-astbuilder:3.0.7:groovy-astbuilder-3.0.7.jar:b290451eb1583666e906c41f7d14747b4cc96363c99c478b244634fd5dfc9013',
|
||||||
|
'org.codehaus.groovy:groovy-cli-picocli:3.0.7:groovy-cli-picocli-3.0.7.jar:71b4bd11fb30a9c7b5618e22122c9c5141958fb27f4dcf0068b6f715088f6916',
|
||||||
|
'org.codehaus.groovy:groovy-console:3.0.7:groovy-console-3.0.7.jar:0541b358b6b8e5363215026736168fccfec1d91bac678d066fa77349eeeaa5dd',
|
||||||
|
'org.codehaus.groovy:groovy-datetime:3.0.7:groovy-datetime-3.0.7.jar:b9823d14b1a4f94236ae2f8a471701aab17e093e1b33402b91550b5c8dd88f04',
|
||||||
|
'org.codehaus.groovy:groovy-docgenerator:3.0.7:groovy-docgenerator-3.0.7.jar:bf53f7a11c9eb1e278e1b8ed2714c741bcf781235c803ad3ba1555f2614573f3',
|
||||||
|
'org.codehaus.groovy:groovy-groovydoc:3.0.7:groovy-groovydoc-3.0.7.jar:86b24dfc23c005066ab83927cdb54177f06c9531773f2e2d2ecc9a131f7c2677',
|
||||||
|
'org.codehaus.groovy:groovy-groovysh:3.0.7:groovy-groovysh-3.0.7.jar:5c40e78cbc09726aedd1c75fab112d245d665d6294870f9119e6cd3013ed14ab',
|
||||||
|
'org.codehaus.groovy:groovy-jmx:3.0.7:groovy-jmx-3.0.7.jar:0a89f3007884eb156751937d93382038b83d39c7c2f0ab156ebf251a7251f2ab',
|
||||||
|
'org.codehaus.groovy:groovy-json:3.0.7:groovy-json-3.0.7.jar:df1f0ee475e3fc93a6a0d17548294e160cca5de6d9d36817a7be1fbe650de03b',
|
||||||
|
'org.codehaus.groovy:groovy-jsr223:3.0.7:groovy-jsr223-3.0.7.jar:1dbd969595332416193baa660fbb45743d19696eaa25fe98e591a2739e13517e',
|
||||||
|
'org.codehaus.groovy:groovy-macro:3.0.7:groovy-macro-3.0.7.jar:c6cc06df526b39e2c359e2435f0071594c5a1c7babafaa6c184fdd8fa931531f',
|
||||||
|
'org.codehaus.groovy:groovy-nio:3.0.7:groovy-nio-3.0.7.jar:db54c577882b294cd8c975ec5451596441baf54781319c61627dca0e0c2361ef',
|
||||||
|
'org.codehaus.groovy:groovy-servlet:3.0.7:groovy-servlet-3.0.7.jar:5b6a909bf501c209adfb6205b9e740649609074455fd979bf9da4853e6ff9a39',
|
||||||
|
'org.codehaus.groovy:groovy-sql:3.0.7:groovy-sql-3.0.7.jar:252bb6c74e1a9f41756ad4fbd3b0d2eddc93bb61109961dd1952a37bf2d57a64',
|
||||||
|
'org.codehaus.groovy:groovy-swing:3.0.7:groovy-swing-3.0.7.jar:bd942032d9328d54c6679c49a41f6caa0d4a0039ebe598493b8a647730d98cff',
|
||||||
|
'org.codehaus.groovy:groovy-templates:3.0.7:groovy-templates-3.0.7.jar:f119e07f650ef186ae5a4b944f9e30915b14311bad47c94a6b32de8d4f69bc80',
|
||||||
|
'org.codehaus.groovy:groovy-test-junit5:3.0.7:groovy-test-junit5-3.0.7.jar:c16eeea07b8e396891e266d7ba9388b24ac804237ffdd9a792b0d08969bad014',
|
||||||
|
'org.codehaus.groovy:groovy-test:3.0.7:groovy-test-3.0.7.jar:f71afd7c25d43017f89ea47e6de6daec971d159047dae083c1513a8422d44b90',
|
||||||
|
'org.codehaus.groovy:groovy-testng:3.0.7:groovy-testng-3.0.7.jar:713d5f2231bbb5712aefd362151b9ffd884aeb7ef2e773315cc54259cbdd063d',
|
||||||
|
'org.codehaus.groovy:groovy-xml:3.0.7:groovy-xml-3.0.7.jar:8a62e7c9ddece3e82676c4bef2f2c100f459602cd1fb6a14e94187bf863e97ff',
|
||||||
|
'org.codehaus.groovy:groovy:3.0.7:groovy-3.0.7.jar:51d1777e8dd1f00e60ea56e00d8a354ff5aab1f00fc8464ae8d39d71867e401f',
|
||||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||||
'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
|
'org.glassfish.jaxb:jaxb-runtime:2.3.2:jaxb-runtime-2.3.2.jar:e6e0a1e89fb6ff786279e6a0082d5cef52dc2ebe67053d041800737652b4fd1b',
|
||||||
'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f',
|
'org.glassfish.jaxb:txw2:2.3.2:txw2-2.3.2.jar:4a6a9f483388d461b81aa9a28c685b8b74c0597993bf1884b04eddbca95f48fe',
|
||||||
'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60',
|
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
||||||
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
||||||
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
||||||
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
||||||
'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40',
|
'org.jacoco:org.jacoco.agent:0.8.3:org.jacoco.agent-0.8.3.jar:522deb254ee16a04cc8341cc8f335f5cb7232982994d961b9cf3a0454709209f',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a',
|
'org.jacoco:org.jacoco.ant:0.8.3:org.jacoco.ant-0.8.3.jar:735844e1ae15f9b875b42a27ac5cb61cc26e106d9e839e5d1c6756709b424ce0',
|
||||||
|
'org.jacoco:org.jacoco.core:0.8.3:org.jacoco.core-0.8.3.jar:0818437bc060a0c7cc798148f22b713702aae2771aba104444407697d578f1ea',
|
||||||
|
'org.jacoco:org.jacoco.report:0.8.3:org.jacoco.report-0.8.3.jar:aae08fa4ff043c807b8876cdb2d8705eb8449a55efce461baa6c09da245088c1',
|
||||||
|
'org.jetbrains.intellij.deps:trove4j:1.0.20181211:trove4j-1.0.20181211.jar:affb7c85a3c87bdcf69ff1dbb84de11f63dc931293934bc08cd7ab18de083601',
|
||||||
|
'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
|
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4',
|
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72:kotlin-stdlib-jdk8-1.3.72.jar:133da70cfc07b56094282eac5c59bccd59f167ee2ead22e5282876d8bc10bf95',
|
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b',
|
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
|
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
|
||||||
|
'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
|
||||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
|
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
|
||||||
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
|
|
||||||
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
||||||
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
|
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
|
||||||
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
|
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
|
||||||
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
|
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
|
||||||
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
|
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
|
||||||
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
||||||
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
|
'org.junit.jupiter:junit-jupiter-api:5.7.0:junit-jupiter-api-5.7.0.jar:b03f78e0daeed2d77a0af9bcd662b4cdb9693f7ee72e01a539b508b84c63d182',
|
||||||
|
'org.junit.jupiter:junit-jupiter-engine:5.7.0:junit-jupiter-engine-5.7.0.jar:dfa26af94644ac2612dde6625852fcb550a0d21caa243257de54cba738ba87af',
|
||||||
|
'org.junit.platform:junit-platform-commons:1.7.0:junit-platform-commons-1.7.0.jar:5330ee87cc7586e6e25175a34e9251624ff12ff525269d3415d0b4ca519b6fea',
|
||||||
|
'org.junit.platform:junit-platform-engine:1.7.0:junit-platform-engine-1.7.0.jar:75f21a20dc594afdc875736725b408cec6d0344874d29f34b2dd3075500236f2',
|
||||||
|
'org.junit.platform:junit-platform-launcher:1.7.0:junit-platform-launcher-1.7.0.jar:fbdc748fde4c4279fe1d3c607447cb3b7ccd45d7338fc574f8a894ddf2d16818',
|
||||||
|
'org.jvnet.staxex:stax-ex:1.8.1:stax-ex-1.8.1.jar:20522549056e9e50aa35ef0b445a2e47a53d06be0b0a9467d704e2483ffb049a',
|
||||||
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
|
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
|
||||||
|
'org.opentest4j:opentest4j:1.2.0:opentest4j-1.2.0.jar:58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2',
|
||||||
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
|
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
|
||||||
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
|
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
|
||||||
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
|
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
|
||||||
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
|
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
|
||||||
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
|
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
|
||||||
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||||
|
'org.testng:testng:7.3.0:testng-7.3.0.jar:63727488f9717d57f0d0a0fee5a1fc10a2be9cfcff2ec3a7187656d663c0774e',
|
||||||
|
'xerces:xercesImpl:2.12.0:xercesImpl-2.12.0.jar:b50d3a4ca502faa4d1c838acb8aa9480446953421f7327e338c5dda3da5e76d0',
|
||||||
|
'xml-apis:xml-apis:1.4.01:xml-apis-1.4.01.jar:a840968176645684bb01aed376e067ab39614885f9eee44abe35a5f20ebe7fad',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ apply from: 'witness.gradle'
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation "com.google.dagger:dagger:$dagger_version"
|
implementation "com.google.dagger:dagger:$dagger_version"
|
||||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||||
|
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
|
||||||
|
|
||||||
testImplementation "junit:junit:$junit_version"
|
testImplementation "junit:junit:$junit_version"
|
||||||
testImplementation "org.jmock:jmock:$jmock_version"
|
testImplementation "org.jmock:jmock:$jmock_version"
|
||||||
testImplementation "org.jmock:jmock-junit4:$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'
|
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,12 @@ public interface FeatureFlags {
|
|||||||
boolean shouldEnableProfilePictures();
|
boolean shouldEnableProfilePictures();
|
||||||
|
|
||||||
boolean shouldEnableDisappearingMessages();
|
boolean shouldEnableDisappearingMessages();
|
||||||
|
|
||||||
|
boolean shouldEnableMailbox();
|
||||||
|
|
||||||
|
boolean shouldEnablePrivateGroupsInCore();
|
||||||
|
|
||||||
|
boolean shouldEnableForumsInCore();
|
||||||
|
|
||||||
|
boolean shouldEnableBlogsInCore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public abstract class UniqueId extends Bytes {
|
public class UniqueId extends Bytes {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of a unique identifier in bytes.
|
* The length of a unique identifier in bytes.
|
||||||
*/
|
*/
|
||||||
public static final int LENGTH = 32;
|
public static final int LENGTH = 32;
|
||||||
|
|
||||||
protected UniqueId(byte[] id) {
|
public UniqueId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
if (id.length != LENGTH) throw new IllegalArgumentException();
|
if (id.length != LENGTH) throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.briarproject.bramble.api;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Provider} that keeps a {@link WeakReference} to the last provided
|
||||||
|
* instance and provides the same instance again until the instance is garbage
|
||||||
|
* collected.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
public abstract class WeakSingletonProvider<T> implements Provider<T> {
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private WeakReference<T> ref = new WeakReference<>(null);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get() {
|
||||||
|
synchronized (lock) {
|
||||||
|
T instance = ref.get();
|
||||||
|
if (instance == null) {
|
||||||
|
instance = createInstance();
|
||||||
|
ref = new WeakReference<>(instance);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
@@ -20,6 +21,8 @@ import java.security.GeneralSecurityException;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ClientHelper {
|
public interface ClientHelper {
|
||||||
|
|
||||||
@@ -123,12 +126,24 @@ public interface ClientHelper {
|
|||||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||||
BdfDictionary properties) throws FormatException;
|
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
|
* Retrieves the contact ID from the group metadata of the given contact
|
||||||
* group.
|
* group.
|
||||||
*/
|
*/
|
||||||
ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||||
throws DbException, FormatException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given contact ID in the group metadata of the given contact
|
* Stores the given contact ID in the group metadata of the given contact
|
||||||
|
|||||||
@@ -107,6 +107,32 @@ public interface ContactManager {
|
|||||||
*/
|
*/
|
||||||
String getHandshakeLink() throws DbException;
|
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
|
* Creates a {@link PendingContact} from the given handshake link and
|
||||||
* alias, adds it to the database and returns it.
|
* alias, adds it to the database and returns it.
|
||||||
@@ -140,11 +166,24 @@ public interface ContactManager {
|
|||||||
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
||||||
throws DbException;
|
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}.
|
* Removes a {@link PendingContact}.
|
||||||
*/
|
*/
|
||||||
void removePendingContact(PendingContactId p) throws DbException;
|
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.
|
* 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.UniqueId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,9 +16,4 @@ public class PendingContactId extends UniqueId {
|
|||||||
public PendingContactId(byte[] id) {
|
public PendingContactId(byte[] id) {
|
||||||
super(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;
|
package org.briarproject.bramble.api.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
@@ -10,6 +11,8 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface CryptoComponent {
|
public interface CryptoComponent {
|
||||||
|
|
||||||
|
UniqueId generateUniqueId();
|
||||||
|
|
||||||
SecretKey generateSecretKey();
|
SecretKey generateSecretKey();
|
||||||
|
|
||||||
SecureRandom getSecureRandom();
|
SecureRandom getSecureRandom();
|
||||||
@@ -170,4 +173,13 @@ public interface CryptoComponent {
|
|||||||
* length. The line terminator is CRLF.
|
* length. The line terminator is CRLF.
|
||||||
*/
|
*/
|
||||||
String asciiArmour(byte[] b, int lineLength);
|
String asciiArmour(byte[] b, int lineLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 encodeOnion(byte[] publicKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,6 +471,14 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
||||||
ContactId c) throws DbException;
|
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
|
* Returns the total length, including headers, of all messages that are
|
||||||
* eligible to be sent to the given contact. This may include messages
|
* 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) {
|
public AuthorId(byte[] id) {
|
||||||
super(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,44 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used by the UI to test the mailbox connection.
|
||||||
|
*
|
||||||
|
* @return true (success) or false (error).
|
||||||
|
* A {@link OwnMailboxConnectionStatusEvent} might be broadcast with a new
|
||||||
|
* {@link MailboxStatus}.
|
||||||
|
*/
|
||||||
|
boolean checkConnection();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxProperties {
|
||||||
|
|
||||||
|
private final String baseUrl;
|
||||||
|
private final MailboxAuthToken authToken;
|
||||||
|
private final boolean owner;
|
||||||
|
|
||||||
|
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
|
||||||
|
boolean owner) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.authToken = authToken;
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOnion() {
|
||||||
|
return baseUrl.replaceFirst("^http://", "")
|
||||||
|
.replaceFirst("\\.onion$", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public MailboxAuthToken getAuthToken() {
|
||||||
|
return authToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
|
||||||
|
|
||||||
|
void recordSuccessfulConnection(Transaction txn, long now)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
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,59 @@
|
|||||||
|
package org.briarproject.bramble.api.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class MailboxStatus {
|
||||||
|
|
||||||
|
private final long lastAttempt, lastSuccess;
|
||||||
|
private final int attemptsSinceSuccess;
|
||||||
|
|
||||||
|
public MailboxStatus(long lastAttempt, long lastSuccess,
|
||||||
|
int attemptsSinceSuccess) {
|
||||||
|
this.lastAttempt = lastAttempt;
|
||||||
|
this.lastSuccess = lastSuccess;
|
||||||
|
this.attemptsSinceSuccess = attemptsSinceSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time of the last attempt to connect to the mailbox, in
|
||||||
|
* milliseconds since the Unix epoch, or -1 if no attempt has been made.
|
||||||
|
* <p>
|
||||||
|
* If an attempt is in progress and has not yet succeeded or failed then
|
||||||
|
* this method returns the time of the previous attempt, or -1 if the
|
||||||
|
* current attempt is the first.
|
||||||
|
*/
|
||||||
|
public long getTimeOfLastAttempt() {
|
||||||
|
return lastAttempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the time of the last successful attempt to connect to the
|
||||||
|
* mailbox, in milliseconds since the Unix epoch, or -1 if no attempt has
|
||||||
|
* succeeded.
|
||||||
|
* <p>
|
||||||
|
* If the last attempt was successful then this method returns the same
|
||||||
|
* value as {@link #getTimeOfLastAttempt()}. If an attempt is in progress
|
||||||
|
* and has not yet succeeded or failed then this method returns the time
|
||||||
|
* of the previous successful connection, or -1 if no attempt has
|
||||||
|
* succeeded.
|
||||||
|
*/
|
||||||
|
public long getTimeOfLastSuccess() {
|
||||||
|
return lastSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of attempts to connect to the mailbox that have
|
||||||
|
* failed since the last attempt succeeded, or the number of attempts that
|
||||||
|
* have been made, if no attempt has ever succeeded.
|
||||||
|
* <p>
|
||||||
|
* If an attempt is in progress and has not yet succeeded or failed then
|
||||||
|
* it is not included in this count.
|
||||||
|
*/
|
||||||
|
public int getAttemptsSinceSuccess() {
|
||||||
|
return attemptsSinceSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,27 @@
|
|||||||
package org.briarproject.bramble.api.plugin;
|
package org.briarproject.bramble.api.plugin;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
public interface TorConstants {
|
public interface TorConstants {
|
||||||
|
|
||||||
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
||||||
|
|
||||||
// Transport properties
|
// Transport properties
|
||||||
String PROP_ONION_V2 = "onion";
|
|
||||||
String PROP_ONION_V3 = "onion3";
|
String PROP_ONION_V3 = "onion3";
|
||||||
|
|
||||||
int DEFAULT_SOCKS_PORT = 59050;
|
int DEFAULT_SOCKS_PORT = 59050;
|
||||||
int DEFAULT_CONTROL_PORT = 59051;
|
int DEFAULT_CONTROL_PORT = 59051;
|
||||||
|
|
||||||
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
int CONNECT_TO_PROXY_TIMEOUT = (int) SECONDS.toMillis(5);
|
||||||
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
|
int EXTRA_CONNECT_TIMEOUT = (int) SECONDS.toMillis(120);
|
||||||
|
int EXTRA_SOCKET_TIMEOUT = (int) SECONDS.toMillis(30);
|
||||||
|
|
||||||
// Local settings (not shared with contacts)
|
// Local settings (not shared with contacts)
|
||||||
String PREF_TOR_NETWORK = "network2";
|
String PREF_TOR_NETWORK = "network2";
|
||||||
String PREF_TOR_PORT = "port";
|
String PREF_TOR_PORT = "port";
|
||||||
String PREF_TOR_MOBILE = "useMobileData";
|
String PREF_TOR_MOBILE = "useMobileData";
|
||||||
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
|
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
|
||||||
String HS_PRIVATE_KEY_V2 = "onionPrivKey";
|
|
||||||
String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
|
String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
|
||||||
String HS_V3_CREATED = "onionPrivKey3Created";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How long to publish a v3 hidden service before retiring the v2 service.
|
|
||||||
*/
|
|
||||||
long V3_MIGRATION_PERIOD_MS = DAYS.toMillis(180);
|
|
||||||
|
|
||||||
// Values for PREF_TOR_NETWORK
|
// Values for PREF_TOR_NETWORK
|
||||||
int PREF_TOR_NETWORK_AUTOMATIC = 0;
|
int PREF_TOR_NETWORK_AUTOMATIC = 0;
|
||||||
|
|||||||
@@ -22,4 +22,11 @@ public interface SettingsManager {
|
|||||||
* namespace.
|
* namespace.
|
||||||
*/
|
*/
|
||||||
void mergeSettings(Settings s, String namespace) throws DbException;
|
void mergeSettings(Settings s, String namespace) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges the given settings with any existing settings in the given
|
||||||
|
* namespace.
|
||||||
|
*/
|
||||||
|
void mergeSettings(Transaction txn, Settings s, String namespace)
|
||||||
|
throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,4 @@ public class GroupId extends UniqueId {
|
|||||||
public GroupId(byte[] id) {
|
public GroupId(byte[] id) {
|
||||||
super(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) {
|
public MessageId(byte[] id) {
|
||||||
super(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
|
* 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
|
* 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
|
@Nullable
|
||||||
StreamContext getStreamContext(TransportId t, byte[] tag)
|
StreamContext getStreamContext(TransportId t, byte[] tag)
|
||||||
throws DbException;
|
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
|
// Despite what the docs say, the return value can be null
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
return ifaces == null ? emptyList() : list(ifaces);
|
return ifaces == null ? emptyList() : list(ifaces);
|
||||||
} catch (SocketException e) {
|
} catch (SocketException | NullPointerException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
package org.briarproject.bramble.test;
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
import org.jmock.Mockery;
|
import org.jmock.Mockery;
|
||||||
|
import org.jmock.lib.concurrent.Synchroniser;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
|
||||||
public abstract class BrambleMockTestCase extends BrambleTestCase {
|
public abstract class BrambleMockTestCase extends BrambleTestCase {
|
||||||
|
|
||||||
protected final Mockery context = new Mockery();
|
protected final Mockery context = new Mockery();
|
||||||
|
|
||||||
|
public BrambleMockTestCase() {
|
||||||
|
context.setThreadingPolicy(new Synchroniser());
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void checkExpectations() {
|
public void checkExpectations() {
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|||||||
@@ -1,17 +1,46 @@
|
|||||||
package org.briarproject.bramble.test;
|
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 {
|
public abstract class BrambleTestCase {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(BrambleTestCase.class.getName());
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected volatile Throwable exceptionInBackgroundThread = null;
|
||||||
|
|
||||||
public BrambleTestCase() {
|
public BrambleTestCase() {
|
||||||
// Ensure exceptions thrown on worker threads cause tests to fail
|
// Ensure exceptions thrown on worker threads cause tests to fail
|
||||||
UncaughtExceptionHandler fail = (thread, throwable) -> {
|
UncaughtExceptionHandler fail = (thread, throwable) -> {
|
||||||
throwable.printStackTrace();
|
LOG.log(WARNING, "Caught unhandled exception", throwable);
|
||||||
fail();
|
exceptionInBackgroundThread = throwable;
|
||||||
};
|
};
|
||||||
Thread.setDefaultUncaughtExceptionHandler(fail);
|
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.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
|
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
|
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.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
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.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
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.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -35,6 +44,8 @@ import java.util.Map;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
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_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_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.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_GROUP_DESCRIPTOR_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_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;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
@@ -209,6 +221,24 @@ public class TestUtils {
|
|||||||
getAgreementPublicKey(), verified);
|
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) {
|
public static double getMedian(Collection<? extends Number> samples) {
|
||||||
int size = samples.size();
|
int size = samples.size();
|
||||||
if (size == 0) throw new IllegalArgumentException();
|
if (size == 0) throw new IllegalArgumentException();
|
||||||
@@ -248,4 +278,27 @@ public class TestUtils {
|
|||||||
return optionalTests != null &&
|
return optionalTests != null &&
|
||||||
asList(optionalTests.split(",")).contains(testClass.getName());
|
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 {
|
dependencyVerification {
|
||||||
verify = [
|
verify = [
|
||||||
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
|
'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: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.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||||
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
|
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
|
||||||
|
|||||||
@@ -10,13 +10,18 @@ apply from: '../dagger.gradle'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':bramble-api', configuration: 'default')
|
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 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
|
||||||
implementation 'org.bitlet:weupnp:0.1.4'
|
implementation 'org.bitlet:weupnp:0.1.4'
|
||||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||||
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
implementation 'org.whispersystems:curve25519-java:0.5.0'
|
||||||
implementation 'org.briarproject:jtorctl:0.3'
|
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"
|
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
|
|
||||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
@@ -25,7 +30,8 @@ dependencies {
|
|||||||
testImplementation "junit:junit:$junit_version"
|
testImplementation "junit:junit:$junit_version"
|
||||||
testImplementation "org.jmock:jmock:$jmock_version"
|
testImplementation "org.jmock:jmock:$jmock_version"
|
||||||
testImplementation "org.jmock:jmock-junit4:$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"
|
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.db.DatabaseExecutorModule;
|
||||||
import org.briarproject.bramble.identity.IdentityModule;
|
import org.briarproject.bramble.identity.IdentityModule;
|
||||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxModule;
|
||||||
import org.briarproject.bramble.plugin.PluginModule;
|
import org.briarproject.bramble.plugin.PluginModule;
|
||||||
import org.briarproject.bramble.properties.PropertiesModule;
|
import org.briarproject.bramble.properties.PropertiesModule;
|
||||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||||
@@ -28,6 +29,8 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
|
|
||||||
void inject(LifecycleModule.EagerSingletons init);
|
void inject(LifecycleModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(MailboxModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(PluginModule.EagerSingletons init);
|
void inject(PluginModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(PropertiesModule.EagerSingletons init);
|
void inject(PropertiesModule.EagerSingletons init);
|
||||||
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||||
c.inject(new IdentityModule.EagerSingletons());
|
c.inject(new IdentityModule.EagerSingletons());
|
||||||
c.inject(new LifecycleModule.EagerSingletons());
|
c.inject(new LifecycleModule.EagerSingletons());
|
||||||
|
c.inject(new MailboxModule.EagerSingletons());
|
||||||
c.inject(new RendezvousModule.EagerSingletons());
|
c.inject(new RendezvousModule.EagerSingletons());
|
||||||
c.inject(new PluginModule.EagerSingletons());
|
c.inject(new PluginModule.EagerSingletons());
|
||||||
c.inject(new PropertiesModule.EagerSingletons());
|
c.inject(new PropertiesModule.EagerSingletons());
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.briarproject.bramble.identity.IdentityModule;
|
|||||||
import org.briarproject.bramble.io.IoModule;
|
import org.briarproject.bramble.io.IoModule;
|
||||||
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
|
import org.briarproject.bramble.mailbox.MailboxModule;
|
||||||
import org.briarproject.bramble.plugin.PluginModule;
|
import org.briarproject.bramble.plugin.PluginModule;
|
||||||
import org.briarproject.bramble.properties.PropertiesModule;
|
import org.briarproject.bramble.properties.PropertiesModule;
|
||||||
import org.briarproject.bramble.record.RecordModule;
|
import org.briarproject.bramble.record.RecordModule;
|
||||||
@@ -43,6 +44,7 @@ import dagger.Module;
|
|||||||
IoModule.class,
|
IoModule.class,
|
||||||
KeyAgreementModule.class,
|
KeyAgreementModule.class,
|
||||||
LifecycleModule.class,
|
LifecycleModule.class,
|
||||||
|
MailboxModule.class,
|
||||||
PluginModule.class,
|
PluginModule.class,
|
||||||
PropertiesModule.class,
|
PropertiesModule.class,
|
||||||
RecordModule.class,
|
RecordModule.class,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.client;
|
package org.briarproject.bramble.client;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
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.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
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.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.util.Base32;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -39,6 +44,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
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.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_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_PROPERTIES_PER_TRANSPORT;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
@@ -399,6 +411,35 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
return tpMap;
|
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
|
@Override
|
||||||
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
|
|||||||
@@ -121,28 +121,40 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHandshakeLink() throws DbException {
|
public String getHandshakeLink() throws DbException {
|
||||||
KeyPair keyPair = db.transactionWithResult(true,
|
return db.transactionWithResult(true, this::getHandshakeLink);
|
||||||
identityManager::getHandshakeKeys);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHandshakeLink(Transaction txn) throws DbException {
|
||||||
|
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
|
||||||
return pendingContactFactory.createHandshakeLink(keyPair.getPublic());
|
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
|
@Override
|
||||||
public PendingContact addPendingContact(String link, String alias)
|
public PendingContact addPendingContact(String link, String alias)
|
||||||
throws DbException, FormatException, GeneralSecurityException {
|
throws DbException, FormatException, GeneralSecurityException {
|
||||||
PendingContact p =
|
|
||||||
pendingContactFactory.createPendingContact(link, alias);
|
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
AuthorId local = identityManager.getLocalAuthor(txn).getId();
|
PendingContact p = addPendingContact(txn, link, alias);
|
||||||
db.addPendingContact(txn, p, local);
|
|
||||||
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
|
||||||
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
|
||||||
ourKeyPair);
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
|
return p;
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
return p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -154,8 +166,14 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Collection<PendingContact> pendingContacts =
|
return db.transactionWithResult(true, this::getPendingContacts);
|
||||||
db.transactionWithResult(true, db::getPendingContacts);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(
|
||||||
|
Transaction txn)
|
||||||
|
throws DbException {
|
||||||
|
Collection<PendingContact> pendingContacts = db.getPendingContacts(txn);
|
||||||
List<Pair<PendingContact, PendingContactState>> pairs =
|
List<Pair<PendingContact, PendingContactState>> pairs =
|
||||||
new ArrayList<>(pendingContacts.size());
|
new ArrayList<>(pendingContacts.size());
|
||||||
for (PendingContact p : pendingContacts) {
|
for (PendingContact p : pendingContacts) {
|
||||||
@@ -168,7 +186,13 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removePendingContact(PendingContactId p) throws DbException {
|
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);
|
states.remove(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
|
|||||||
import org.bouncycastle.crypto.CryptoException;
|
import org.bouncycastle.crypto.CryptoException;
|
||||||
import org.bouncycastle.crypto.Digest;
|
import org.bouncycastle.crypto.Digest;
|
||||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
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.AgreementPrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
@@ -21,11 +23,13 @@ import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
|
|||||||
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
|
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
|
import org.briarproject.bramble.util.Base32;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.whispersystems.curve25519.Curve25519;
|
import org.whispersystems.curve25519.Curve25519;
|
||||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
@@ -38,6 +42,7 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.lang.System.arraycopy;
|
import static java.lang.System.arraycopy;
|
||||||
import static java.util.logging.Level.INFO;
|
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_AGREEMENT;
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
|
||||||
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
|
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
|
||||||
@@ -51,13 +56,15 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
|||||||
class CryptoComponentImpl implements CryptoComponent {
|
class CryptoComponentImpl implements CryptoComponent {
|
||||||
|
|
||||||
private static final Logger LOG =
|
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 SIGNATURE_KEY_PAIR_BITS = 256;
|
||||||
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
||||||
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
||||||
private static final byte PBKDF_FORMAT_SCRYPT = 0;
|
private static final byte PBKDF_FORMAT_SCRYPT = 0;
|
||||||
private static final byte PBKDF_FORMAT_SCRYPT_STRENGTHENED = 1;
|
private static final byte PBKDF_FORMAT_SCRYPT_STRENGTHENED = 1;
|
||||||
|
private static final byte ONION_HS_PROTOCOL_VERSION = 3;
|
||||||
|
private static final int ONION_CHECKSUM_BYTES = 2;
|
||||||
|
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final PasswordBasedKdf passwordBasedKdf;
|
private final PasswordBasedKdf passwordBasedKdf;
|
||||||
@@ -123,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
|
@Override
|
||||||
public SecretKey generateSecretKey() {
|
public SecretKey generateSecretKey() {
|
||||||
byte[] b = new byte[SecretKey.LENGTH];
|
byte[] b = new byte[SecretKey.LENGTH];
|
||||||
@@ -442,4 +456,21 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
public String asciiArmour(byte[] b, int lineLength) {
|
public String asciiArmour(byte[] b, int lineLength) {
|
||||||
return AsciiArmour.wrap(b, lineLength);
|
return AsciiArmour.wrap(b, lineLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
digest.update(publicKey, 0, publicKey.length);
|
||||||
|
digest.update(ONION_HS_PROTOCOL_VERSION);
|
||||||
|
byte[] checksum = new byte[digest.getDigestSize()];
|
||||||
|
digest.doFinal(checksum, 0);
|
||||||
|
byte[] address = new byte[publicKey.length + ONION_CHECKSUM_BYTES + 1];
|
||||||
|
arraycopy(publicKey, 0, address, 0, publicKey.length);
|
||||||
|
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
|
||||||
|
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
|
||||||
|
return Base32.encode(address).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -757,6 +757,14 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
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
|
* Sets the cleanup timer duration for the given message. This does not
|
||||||
* start the message's cleanup timer.
|
* start the message's cleanup timer.
|
||||||
@@ -841,12 +849,14 @@ interface Database<T> {
|
|||||||
void stopCleanupTimer(T txn, MessageId m) throws DbException;
|
void stopCleanupTimer(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the transmission count, expiry time and estimated time of arrival
|
* Updates the transmission count, expiry time and max latency of the given
|
||||||
* of the given message with respect to the given contact, using the latency
|
* message with respect to the given contact.
|
||||||
* of the transport over which it was sent.
|
*
|
||||||
|
* @param maxLatency latency of the transport over which the message was
|
||||||
|
* sent.
|
||||||
*/
|
*/
|
||||||
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, long maxLatency)
|
void updateRetransmissionData(T txn, ContactId c, MessageId m,
|
||||||
throws DbException;
|
long maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given transport keys, deleting any keys they have replaced.
|
* 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);
|
Message message = db.getMessage(txn, m);
|
||||||
totalLength += message.getRawLength();
|
totalLength += message.getRawLength();
|
||||||
messages.add(message);
|
messages.add(message);
|
||||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
@@ -462,7 +462,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
totalLength += message.getRawLength();
|
totalLength += message.getRawLength();
|
||||||
messages.add(message);
|
messages.add(message);
|
||||||
sentIds.add(m);
|
sentIds.add(m);
|
||||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (messages.isEmpty()) return messages;
|
if (messages.isEmpty()) return messages;
|
||||||
@@ -483,7 +483,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
|
db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
|
||||||
if (ids.isEmpty()) return null;
|
if (ids.isEmpty()) return null;
|
||||||
for (MessageId m : ids)
|
for (MessageId m : ids)
|
||||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
return new Offer(ids);
|
return new Offer(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +518,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
Message message = db.getMessage(txn, m);
|
Message message = db.getMessage(txn, m);
|
||||||
totalLength += message.getRawLength();
|
totalLength += message.getRawLength();
|
||||||
messages.add(message);
|
messages.add(message);
|
||||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
db.updateRetransmissionData(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
@@ -750,6 +750,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getUnackedMessagesToSend(txn, c);
|
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
|
@Override
|
||||||
public long getUnackedMessageBytesToSend(Transaction transaction,
|
public long getUnackedMessageBytesToSend(Transaction transaction,
|
||||||
ContactId c) throws DbException {
|
ContactId c) throws DbException {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package org.briarproject.bramble.db;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
|
||||||
|
|
||||||
interface DatabaseConstants {
|
interface DatabaseConstants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,19 +23,6 @@ interface DatabaseConstants {
|
|||||||
*/
|
*/
|
||||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
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
|
* The {@link Settings} key under which the flag is stored indicating
|
||||||
* whether the database is marked as dirty.
|
* whether the database is marked as dirty.
|
||||||
|
|||||||
@@ -85,12 +85,17 @@ class H2Database extends JdbcDatabase {
|
|||||||
public void close() throws DbException {
|
public void close() throws DbException {
|
||||||
// H2 will close the database when the last connection closes
|
// H2 will close the database when the last connection closes
|
||||||
Connection c = null;
|
Connection c = null;
|
||||||
|
Statement s = null;
|
||||||
try {
|
try {
|
||||||
c = createConnection();
|
c = createConnection();
|
||||||
super.closeAllConnections();
|
closeAllConnections();
|
||||||
setDirty(c, false);
|
setDirty(c, false);
|
||||||
|
s = c.createStatement();
|
||||||
|
s.execute("SHUTDOWN COMPACT");
|
||||||
|
s.close();
|
||||||
c.close();
|
c.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
tryToClose(s, LOG, WARNING);
|
||||||
tryToClose(c, LOG, WARNING);
|
tryToClose(c, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,11 +79,11 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
Connection c = null;
|
Connection c = null;
|
||||||
Statement s = null;
|
Statement s = null;
|
||||||
try {
|
try {
|
||||||
super.closeAllConnections();
|
closeAllConnections();
|
||||||
c = createConnection();
|
c = createConnection();
|
||||||
setDirty(c, false);
|
setDirty(c, false);
|
||||||
s = c.createStatement();
|
s = c.createStatement();
|
||||||
s.executeQuery("SHUTDOWN");
|
s.executeQuery("SHUTDOWN COMPACT");
|
||||||
s.close();
|
s.close();
|
||||||
c.close();
|
c.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -106,7 +106,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
Connection c = null;
|
Connection c = null;
|
||||||
Statement s = null;
|
Statement s = null;
|
||||||
try {
|
try {
|
||||||
super.closeAllConnections();
|
closeAllConnections();
|
||||||
c = createConnection();
|
c = createConnection();
|
||||||
s = c.createStatement();
|
s = c.createStatement();
|
||||||
s.executeQuery("SHUTDOWN COMPACT");
|
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.api.sync.validation.MessageState.UNKNOWN;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
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.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.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
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> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
// Package access for testing
|
// 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
|
// Time period offsets for incoming transport keys
|
||||||
private static final int OFFSET_PREV = -1;
|
private static final int OFFSET_PREV = -1;
|
||||||
@@ -252,7 +250,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " requested BOOLEAN NOT NULL,"
|
+ " requested BOOLEAN NOT NULL,"
|
||||||
+ " expiry BIGINT NOT NULL,"
|
+ " expiry BIGINT NOT NULL,"
|
||||||
+ " txCount INT NOT NULL,"
|
+ " txCount INT NOT NULL,"
|
||||||
+ " eta BIGINT NOT NULL,"
|
+ " maxLatency BIGINT," // Null if latency was reset
|
||||||
+ " PRIMARY KEY (messageId, contactId),"
|
+ " PRIMARY KEY (messageId, contactId),"
|
||||||
+ " FOREIGN KEY (messageId)"
|
+ " FOREIGN KEY (messageId)"
|
||||||
+ " REFERENCES messages (messageId)"
|
+ " REFERENCES messages (messageId)"
|
||||||
@@ -378,8 +376,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
throws DbException, SQLException;
|
throws DbException, SQLException;
|
||||||
|
|
||||||
// Used exclusively during open to compact the database after schema
|
// Used exclusively during open to compact the database after schema
|
||||||
// migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
|
// migrations or if the database was not shut down cleanly
|
||||||
// elapsed
|
|
||||||
protected abstract void compactAndClose() throws DbException;
|
protected abstract void compactAndClose() throws DbException;
|
||||||
|
|
||||||
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||||
@@ -405,7 +402,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (reopen) {
|
if (reopen) {
|
||||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||||
wasDirtyOnInitialisation = isDirty(s);
|
wasDirtyOnInitialisation = isDirty(s);
|
||||||
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
|
boolean migrated = migrateSchema(txn, s, listener);
|
||||||
|
compact = wasDirtyOnInitialisation || migrated;
|
||||||
} else {
|
} else {
|
||||||
wasDirtyOnInitialisation = false;
|
wasDirtyOnInitialisation = false;
|
||||||
createTables(txn);
|
createTables(txn);
|
||||||
@@ -429,16 +427,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
compactAndClose();
|
compactAndClose();
|
||||||
logDuration(LOG, "Compacting database", start);
|
logDuration(LOG, "Compacting database", start);
|
||||||
// Allow the next transaction to reopen the DB
|
// Allow the next transaction to reopen the DB
|
||||||
synchronized (connectionsLock) {
|
connectionsLock.lock();
|
||||||
closed = false;
|
|
||||||
}
|
|
||||||
txn = startTransaction();
|
|
||||||
try {
|
try {
|
||||||
storeLastCompacted(txn);
|
closed = false;
|
||||||
commitTransaction(txn);
|
} finally {
|
||||||
} catch (DbException e) {
|
connectionsLock.unlock();
|
||||||
abortTransaction(txn);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,18 +492,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
new Migration45_46(),
|
new Migration45_46(),
|
||||||
new Migration46_47(dbTypes),
|
new Migration46_47(dbTypes),
|
||||||
new Migration47_48(),
|
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)
|
private void storeSchemaVersion(Connection txn, int version)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
@@ -518,12 +504,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
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) {
|
private boolean isDirty(Settings s) {
|
||||||
return s.getBoolean(DIRTY_KEY, false);
|
return s.getBoolean(DIRTY_KEY, false);
|
||||||
}
|
}
|
||||||
@@ -537,7 +517,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private void initialiseSettings(Connection txn) throws DbException {
|
private void initialiseSettings(Connection txn) throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
||||||
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
|
|
||||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,9 +896,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
||||||
+ " timestamp, length, state, groupShared, messageShared,"
|
+ " timestamp, length, state, groupShared, messageShared,"
|
||||||
+ " deleted, ack, seen, requested, expiry, txCount, eta)"
|
+ " deleted, ack, seen, requested, expiry, txCount,"
|
||||||
|
+ " maxLatency)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0,"
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0,"
|
||||||
+ " 0)";
|
+ " NULL)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getBytes());
|
ps.setBytes(1, m.getBytes());
|
||||||
ps.setInt(2, c.getInt());
|
ps.setInt(2, c.getInt());
|
||||||
@@ -1153,17 +1133,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
} else {
|
} else {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long eta = now + maxLatency;
|
|
||||||
sql = "SELECT NULL FROM statuses"
|
sql = "SELECT NULL FROM statuses"
|
||||||
+ " WHERE contactId = ? AND state = ?"
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE AND seen = FALSE"
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
+ " AND (expiry <= ? OR eta > ?)";
|
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||||
|
+ " OR ? < maxLatency)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
ps.setLong(3, now);
|
ps.setLong(3, now);
|
||||||
ps.setLong(4, eta);
|
ps.setLong(4, maxLatency);
|
||||||
}
|
}
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
boolean messagesToSend = rs.next();
|
boolean messagesToSend = rs.next();
|
||||||
@@ -2191,7 +2171,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
public Collection<MessageId> getMessagesToOffer(Connection txn,
|
public Collection<MessageId> getMessagesToOffer(Connection txn,
|
||||||
ContactId c, int maxMessages, long maxLatency) throws DbException {
|
ContactId c, int maxMessages, long maxLatency) throws DbException {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long eta = now + maxLatency;
|
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -2200,13 +2179,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE"
|
+ " AND deleted = FALSE"
|
||||||
+ " AND seen = FALSE AND requested = FALSE"
|
+ " AND seen = FALSE AND requested = FALSE"
|
||||||
+ " AND (expiry <= ? OR eta > ?)"
|
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||||
|
+ " OR ? < maxLatency)"
|
||||||
+ " ORDER BY timestamp LIMIT ?";
|
+ " ORDER BY timestamp LIMIT ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
ps.setLong(3, now);
|
ps.setLong(3, now);
|
||||||
ps.setLong(4, eta);
|
ps.setLong(4, maxLatency);
|
||||||
ps.setInt(5, maxMessages);
|
ps.setInt(5, maxMessages);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
@@ -2250,7 +2230,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
|
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
|
||||||
int maxLength, long maxLatency) throws DbException {
|
int maxLength, long maxLatency) throws DbException {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long eta = now + maxLatency;
|
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -2259,13 +2238,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE"
|
+ " AND deleted = FALSE"
|
||||||
+ " AND seen = FALSE"
|
+ " AND seen = FALSE"
|
||||||
+ " AND (expiry <= ? OR eta > ?)"
|
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||||
|
+ " OR ? < maxLatency)"
|
||||||
+ " ORDER BY timestamp";
|
+ " ORDER BY timestamp";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
ps.setLong(3, now);
|
ps.setLong(3, now);
|
||||||
ps.setLong(4, eta);
|
ps.setLong(4, maxLatency);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
int total = 0;
|
int total = 0;
|
||||||
@@ -2549,7 +2529,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
|
||||||
ContactId c, int maxLength, long maxLatency) throws DbException {
|
ContactId c, int maxLength, long maxLatency) throws DbException {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long eta = now + maxLatency;
|
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -2558,13 +2537,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
+ " AND deleted = FALSE"
|
+ " AND deleted = FALSE"
|
||||||
+ " AND seen = FALSE AND requested = TRUE"
|
+ " AND seen = FALSE AND requested = TRUE"
|
||||||
+ " AND (expiry <= ? OR eta > ?)"
|
+ " AND (expiry <= ? OR maxLatency IS NULL"
|
||||||
|
+ " OR ? < maxLatency)"
|
||||||
+ " ORDER BY timestamp";
|
+ " ORDER BY timestamp";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setInt(2, DELIVERED.getValue());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
ps.setLong(3, now);
|
ps.setLong(3, now);
|
||||||
ps.setLong(4, eta);
|
ps.setLong(4, maxLatency);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
List<MessageId> ids = new ArrayList<>();
|
||||||
int total = 0;
|
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
|
@Override
|
||||||
public void setCleanupTimerDuration(Connection txn, MessageId m,
|
public void setCleanupTimerDuration(Connection txn, MessageId m,
|
||||||
long duration) throws DbException {
|
long duration) throws DbException {
|
||||||
@@ -3617,8 +3621,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
|
public void updateRetransmissionData(Connection txn, ContactId c,
|
||||||
long maxLatency) throws DbException {
|
MessageId m, long maxLatency) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -3634,13 +3638,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
sql = "UPDATE statuses"
|
sql = "UPDATE statuses"
|
||||||
+ " SET expiry = ?, txCount = txCount + 1, eta = ?"
|
+ " SET expiry = ?, txCount = txCount + 1, maxLatency = ?"
|
||||||
+ " WHERE messageId = ? AND contactId = ?";
|
+ " WHERE messageId = ? AND contactId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long eta = now + maxLatency;
|
|
||||||
ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
|
ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
|
||||||
ps.setLong(2, eta);
|
ps.setLong(2, maxLatency);
|
||||||
ps.setBytes(3, m.getBytes());
|
ps.setBytes(3, m.getBytes());
|
||||||
ps.setInt(4, c.getInt());
|
ps.setInt(4, c.getInt());
|
||||||
int affected = ps.executeUpdate();
|
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;
|
package org.briarproject.bramble.io;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||||
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
import org.briarproject.bramble.api.io.TimeoutMonitor;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
import okhttp3.Dns;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class IoModule {
|
public class IoModule {
|
||||||
|
|
||||||
|
private static final int CONNECT_TIMEOUT = 60_000; // Milliseconds
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
|
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
|
||||||
return 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,134 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxManagerImpl implements MailboxManager {
|
||||||
|
|
||||||
|
private static final String TAG = MailboxManagerImpl.class.getName();
|
||||||
|
private final static Logger LOG = getLogger(TAG);
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final MailboxApi api;
|
||||||
|
private final TransactionManager db;
|
||||||
|
private final MailboxSettingsManager mailboxSettingsManager;
|
||||||
|
private final MailboxPairingTaskFactory pairingTaskFactory;
|
||||||
|
private final Clock clock;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private MailboxPairingTask pairingTask = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxManagerImpl(
|
||||||
|
@IoExecutor Executor ioExecutor,
|
||||||
|
MailboxApi api,
|
||||||
|
TransactionManager db,
|
||||||
|
MailboxSettingsManager mailboxSettingsManager,
|
||||||
|
MailboxPairingTaskFactory pairingTaskFactory,
|
||||||
|
Clock clock) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.api = api;
|
||||||
|
this.db = db;
|
||||||
|
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||||
|
this.pairingTaskFactory = pairingTaskFactory;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkConnection() {
|
||||||
|
boolean success;
|
||||||
|
try {
|
||||||
|
MailboxProperties props = db.transactionWithNullableResult(true,
|
||||||
|
mailboxSettingsManager::getOwnMailboxProperties);
|
||||||
|
success = api.checkStatus(props);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// we don't treat this is a failure to record
|
||||||
|
return false;
|
||||||
|
} catch (IOException | MailboxApi.ApiException e) {
|
||||||
|
// we record this as a failure
|
||||||
|
success = false;
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
recordCheckResult(success);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordCheckResult(boolean success) throws DbException {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
db.transaction(false, txn -> {
|
||||||
|
if (success) {
|
||||||
|
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
|
||||||
|
} else {
|
||||||
|
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +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,303 @@
|
|||||||
|
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));
|
||||||
|
// Reset message retransmission timers for the contact. Avoiding
|
||||||
|
// messages getting stranded:
|
||||||
|
// - on our mailbox, if they now have a mailbox but didn't before
|
||||||
|
// - on the contact's old mailbox, if they removed their mailbox
|
||||||
|
// - on the contact's old mailbox, if they replaced their mailbox
|
||||||
|
db.resetUnackedMessagesToSend(txn, c);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
|
||||||
|
|
||||||
|
// Package access for testing
|
||||||
|
static final String SETTINGS_NAMESPACE = "mailbox";
|
||||||
|
static final String SETTINGS_KEY_ONION = "onion";
|
||||||
|
static final String SETTINGS_KEY_TOKEN = "token";
|
||||||
|
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 {
|
||||||
|
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
String onion = s.get(SETTINGS_KEY_ONION);
|
||||||
|
String token = s.get(SETTINGS_KEY_TOKEN);
|
||||||
|
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
|
||||||
|
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.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
|
||||||
|
public MailboxStatus getOwnMailboxStatus(Transaction txn)
|
||||||
|
throws DbException {
|
||||||
|
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
|
||||||
|
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
|
||||||
|
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
|
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recordSuccessfulConnection(Transaction txn, long now)
|
||||||
|
throws DbException {
|
||||||
|
Settings s = new Settings();
|
||||||
|
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
|
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||||
|
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
|
MailboxStatus status = new MailboxStatus(now, now, 0);
|
||||||
|
txn.attach(new OwnMailboxConnectionStatusEvent(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||||
|
throws DbException {
|
||||||
|
Settings oldSettings =
|
||||||
|
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
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, 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.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||||
|
|
||||||
|
@Deprecated // We can simply remove tasks when they finish
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface RemovableDriveTaskRegistry {
|
interface RemovableDriveTaskRegistry {
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,73 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// TODO: Create a module for this so it doesn't have to be public
|
@NotNullByDefault
|
||||||
|
|
||||||
public interface CircumventionProvider {
|
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.
|
* 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
|
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||||
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
|
* 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.
|
* Countries where bridge connections are likely to work.
|
||||||
* Should be a subset of {@link #BLOCKED}.
|
* 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}.
|
* 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);
|
boolean isTorProbablyBlocked(String countryCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if bridge connections of some type work in the given
|
||||||
|
* country.
|
||||||
|
*/
|
||||||
boolean doBridgesWork(String countryCode);
|
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
|
@IoExecutor
|
||||||
List<String> getBridges(boolean meek);
|
List<String> getBridges(BridgeType type);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -9,24 +10,33 @@ import java.util.List;
|
|||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
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 {
|
class CircumventionProviderImpl implements CircumventionProvider {
|
||||||
|
|
||||||
private final static String BRIDGE_FILE_NAME = "bridges";
|
private final static String BRIDGE_FILE_NAME = "bridges";
|
||||||
|
|
||||||
private static final Set<String> BLOCKED_IN_COUNTRIES =
|
private static final Set<String> BLOCKED_IN_COUNTRIES =
|
||||||
new HashSet<>(asList(BLOCKED));
|
new HashSet<>(asList(BLOCKED));
|
||||||
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
|
private static final Set<String> BRIDGE_COUNTRIES =
|
||||||
new HashSet<>(asList(BRIDGES));
|
new HashSet<>(asList(BRIDGES));
|
||||||
private static final Set<String> BRIDGES_NEED_MEEK =
|
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
|
||||||
new HashSet<>(asList(NEEDS_MEEK));
|
new HashSet<>(asList(DEFAULT_BRIDGES));
|
||||||
|
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
|
||||||
@Nullable
|
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
|
||||||
private volatile List<String> bridges = null;
|
private static final Set<String> MEEK_COUNTRIES =
|
||||||
|
new HashSet<>(asList(MEEK_BRIDGES));
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CircumventionProviderImpl() {
|
CircumventionProviderImpl() {
|
||||||
@@ -39,33 +49,40 @@ class CircumventionProviderImpl implements CircumventionProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean doBridgesWork(String countryCode) {
|
public boolean doBridgesWork(String countryCode) {
|
||||||
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
|
return BRIDGE_COUNTRIES.contains(countryCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean needsMeek(String countryCode) {
|
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
|
||||||
return BRIDGES_NEED_MEEK.contains(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
|
@Override
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
public List<String> getBridges(boolean useMeek) {
|
public List<String> getBridges(BridgeType type) {
|
||||||
List<String> bridges = this.bridges;
|
InputStream is = requireNonNull(getClass().getClassLoader()
|
||||||
if (bridges != null) return new ArrayList<>(bridges);
|
.getResourceAsStream(BRIDGE_FILE_NAME));
|
||||||
|
|
||||||
InputStream is = getClass().getClassLoader()
|
|
||||||
.getResourceAsStream(BRIDGE_FILE_NAME);
|
|
||||||
Scanner scanner = new Scanner(is);
|
Scanner scanner = new Scanner(is);
|
||||||
|
|
||||||
bridges = new ArrayList<>();
|
List<String> bridges = new ArrayList<>();
|
||||||
while (scanner.hasNextLine()) {
|
while (scanner.hasNextLine()) {
|
||||||
String line = scanner.nextLine();
|
String line = scanner.nextLine();
|
||||||
boolean isMeekBridge = line.startsWith("Bridge meek");
|
if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
|
||||||
if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue;
|
(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
|
||||||
if (!line.startsWith("#")) bridges.add(line);
|
(type == VANILLA && line.startsWith("v ")) ||
|
||||||
|
(type == MEEK && line.startsWith("m "))) {
|
||||||
|
bridges.add(line.substring(2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
scanner.close();
|
scanner.close();
|
||||||
this.bridges = bridges;
|
|
||||||
return 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.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
@@ -79,23 +80,21 @@ import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_PLUG
|
|||||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
|
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
|
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
|
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V2;
|
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V3;
|
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V3;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.HS_V3_CREATED;
|
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
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_MOBILE;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
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_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_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_ONLY_WHEN_CHARGING;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
|
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
|
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
|
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.V3_MIGRATION_PERIOD_MS;
|
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.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
|
||||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
@@ -110,12 +109,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private static final Logger LOG = getLogger(TorPlugin.class.getName());
|
private static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private static final String[] EVENTS = {
|
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 String OWNER = "__OwningControllerProcess";
|
||||||
private static final int COOKIE_TIMEOUT_MS = 3000;
|
private static final int COOKIE_TIMEOUT_MS = 3000;
|
||||||
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
|
||||||
private static final Pattern ONION_V2 = Pattern.compile("[a-z2-7]{16}");
|
|
||||||
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
|
||||||
|
|
||||||
private final Executor ioExecutor, wakefulIoExecutor;
|
private final Executor ioExecutor, wakefulIoExecutor;
|
||||||
@@ -135,8 +140,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private final int maxIdleTime;
|
private final int maxIdleTime;
|
||||||
private final int socketTimeout;
|
private final int socketTimeout;
|
||||||
private final File torDirectory, geoIpFile, configFile;
|
private final File torDirectory, geoIpFile, configFile;
|
||||||
private int torSocksPort;
|
private final int torSocksPort;
|
||||||
private int torControlPort;
|
private final int torControlPort;
|
||||||
private final File doneFile, cookieFile;
|
private final File doneFile, cookieFile;
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
@@ -231,7 +236,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Load the settings
|
// Load the settings
|
||||||
settings = migrateSettings(callback.getSettings());
|
settings = callback.getSettings();
|
||||||
// Install or update the assets if necessary
|
// Install or update the assets if necessary
|
||||||
if (!assetsAreUpToDate()) installAssets();
|
if (!assetsAreUpToDate()) installAssets();
|
||||||
if (cookieFile.exists() && !cookieFile.delete())
|
if (cookieFile.exists() && !cookieFile.delete())
|
||||||
@@ -284,6 +289,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
|
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
|
||||||
throw new PluginException();
|
throw new PluginException();
|
||||||
}
|
}
|
||||||
|
//noinspection BusyWait
|
||||||
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
|
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
LOG.info("Auth cookie created");
|
LOG.info("Auth cookie created");
|
||||||
@@ -304,11 +310,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
controlConnection.setEventHandler(this);
|
controlConnection.setEventHandler(this);
|
||||||
controlConnection.setEvents(asList(EVENTS));
|
controlConnection.setEvents(asList(EVENTS));
|
||||||
// Check whether Tor has already bootstrapped
|
// Check whether Tor has already bootstrapped
|
||||||
String phase = controlConnection.getInfo("status/bootstrap-phase");
|
String info = controlConnection.getInfo("status/bootstrap-phase");
|
||||||
if (phase != null && phase.contains("PROGRESS=100")) {
|
if (info != null && info.contains("PROGRESS=100")) {
|
||||||
LOG.info("Tor has already bootstrapped");
|
LOG.info("Tor has already bootstrapped");
|
||||||
state.setBootstrapped();
|
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) {
|
} catch (IOException e) {
|
||||||
throw new PluginException(e);
|
throw new PluginException(e);
|
||||||
}
|
}
|
||||||
@@ -320,18 +332,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
bind();
|
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() {
|
private boolean assetsAreUpToDate() {
|
||||||
return doneFile.lastModified() > getLastUpdateTime();
|
return doneFile.lastModified() > getLastUpdateTime();
|
||||||
}
|
}
|
||||||
@@ -341,9 +341,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
// The done file may already exist from a previous installation
|
// The done file may already exist from a previous installation
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
doneFile.delete();
|
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();
|
installTorExecutable();
|
||||||
installObfs4Executable();
|
installObfs4Executable();
|
||||||
extract(getGeoIpInputStream(), geoIpFile);
|
|
||||||
extract(getConfigInputStream(), configFile);
|
extract(getConfigInputStream(), configFile);
|
||||||
if (!doneFile.createNewFile())
|
if (!doneFile.createNewFile())
|
||||||
LOG.warning("Failed to create done file");
|
LOG.warning("Failed to create done file");
|
||||||
@@ -381,14 +386,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return zin;
|
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 {
|
private InputStream getObfs4InputStream() throws IOException {
|
||||||
InputStream in = resourceProvider
|
InputStream in = resourceProvider
|
||||||
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
|
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
|
||||||
@@ -412,6 +409,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
append(strb, "RunAsDaemon", 1);
|
append(strb, "RunAsDaemon", 1);
|
||||||
append(strb, "SafeSocks", 1);
|
append(strb, "SafeSocks", 1);
|
||||||
append(strb, "SocksPort", torSocksPort);
|
append(strb, "SocksPort", torSocksPort);
|
||||||
|
//noinspection CharsetObjectCanBeUsed
|
||||||
return new ByteArrayInputStream(
|
return new ByteArrayInputStream(
|
||||||
strb.toString().getBytes(Charset.forName("UTF-8")));
|
strb.toString().getBytes(Charset.forName("UTF-8")));
|
||||||
}
|
}
|
||||||
@@ -478,54 +476,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
private void publishHiddenService(String port) {
|
private void publishHiddenService(String port) {
|
||||||
if (!state.isTorRunning()) return;
|
if (!state.isTorRunning()) return;
|
||||||
// TODO: Remove support for v2 hidden services after a reasonable
|
|
||||||
// migration period (migration started 2020-06-30)
|
|
||||||
String privKey2 = settings.get(HS_PRIVATE_KEY_V2);
|
|
||||||
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
|
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
|
||||||
String v3Created = settings.get(HS_V3_CREATED);
|
|
||||||
// Publish a v2 hidden service if we've already created one, and
|
|
||||||
// either we've never published a v3 hidden service or we're still
|
|
||||||
// in the migration period since first publishing it
|
|
||||||
if (!isNullOrEmpty(privKey2)) {
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
long then = v3Created == null ? now : Long.parseLong(v3Created);
|
|
||||||
if (now - then >= V3_MIGRATION_PERIOD_MS) retireV2HiddenService();
|
|
||||||
else publishV2HiddenService(port, privKey2);
|
|
||||||
}
|
|
||||||
publishV3HiddenService(port, privKey3);
|
publishV3HiddenService(port, privKey3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publishV2HiddenService(String port, String privKey) {
|
|
||||||
LOG.info("Creating v2 hidden service");
|
|
||||||
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
|
|
||||||
Map<String, String> response;
|
|
||||||
try {
|
|
||||||
response = controlConnection.addOnion(privKey, portLines);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!response.containsKey(HS_ADDRESS)) {
|
|
||||||
LOG.warning("Tor did not return a hidden service address");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String onion2 = response.get(HS_ADDRESS);
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("V2 hidden service " + scrubOnion(onion2));
|
|
||||||
}
|
|
||||||
// The hostname has already been published and the private key stored
|
|
||||||
}
|
|
||||||
|
|
||||||
private void retireV2HiddenService() {
|
|
||||||
LOG.info("Retiring v2 hidden service");
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_ONION_V2, "");
|
|
||||||
callback.mergeLocalProperties(p);
|
|
||||||
Settings s = new Settings();
|
|
||||||
s.put(HS_PRIVATE_KEY_V2, "");
|
|
||||||
callback.mergeSettings(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publishV3HiddenService(String port, @Nullable String privKey) {
|
private void publishV3HiddenService(String port, @Nullable String privKey) {
|
||||||
LOG.info("Creating v3 hidden service");
|
LOG.info("Creating v3 hidden service");
|
||||||
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
|
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
|
||||||
@@ -562,7 +516,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
// Save the hidden service's private key for next time
|
// Save the hidden service's private key for next time
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
|
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
|
||||||
s.put(HS_V3_CREATED, String.valueOf(clock.currentTimeMillis()));
|
|
||||||
callback.mergeSettings(s);
|
callback.mergeSettings(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,20 +543,24 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableBridges(boolean enable, boolean needsMeek)
|
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
Collection<String> conf = new ArrayList<>();
|
Collection<String> conf = new ArrayList<>();
|
||||||
conf.add("UseBridges 1");
|
conf.add("UseBridges 1");
|
||||||
File obfs4File = getObfs4ExecutableFile();
|
File obfs4File = getObfs4ExecutableFile();
|
||||||
if (needsMeek) {
|
if (bridgeTypes.contains(MEEK)) {
|
||||||
conf.add("ClientTransportPlugin meek_lite exec " +
|
conf.add("ClientTransportPlugin meek_lite exec " +
|
||||||
obfs4File.getAbsolutePath());
|
obfs4File.getAbsolutePath());
|
||||||
} else {
|
}
|
||||||
|
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
|
||||||
|
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
|
||||||
conf.add("ClientTransportPlugin obfs4 exec " +
|
conf.add("ClientTransportPlugin obfs4 exec " +
|
||||||
obfs4File.getAbsolutePath());
|
obfs4File.getAbsolutePath());
|
||||||
}
|
}
|
||||||
conf.addAll(circumventionProvider.getBridges(needsMeek));
|
for (BridgeType bridgeType : bridgeTypes) {
|
||||||
|
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
||||||
|
}
|
||||||
controlConnection.setConf(conf);
|
controlConnection.setConf(conf);
|
||||||
} else {
|
} else {
|
||||||
controlConnection.setConf("UseBridges", "0");
|
controlConnection.setConf("UseBridges", "0");
|
||||||
@@ -669,49 +626,30 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createConnection(TransportProperties p) {
|
public DuplexTransportConnection createConnection(TransportProperties p) {
|
||||||
if (getState() != ACTIVE) return null;
|
if (getState() != ACTIVE) return null;
|
||||||
// TODO: Remove support for v2 hidden services after a reasonable
|
|
||||||
// migration period (migration started 2020-06-30)
|
|
||||||
String bestOnion = null, version = null;
|
|
||||||
String onion2 = p.get(PROP_ONION_V2);
|
|
||||||
String onion3 = p.get(PROP_ONION_V3);
|
String onion3 = p.get(PROP_ONION_V3);
|
||||||
if (!isNullOrEmpty(onion2)) {
|
if (onion3 != null && !ONION_V3.matcher(onion3).matches()) {
|
||||||
if (ONION_V2.matcher(onion2).matches()) {
|
// Don't scrub the address so we can find the problem
|
||||||
bestOnion = onion2;
|
if (LOG.isLoggable(INFO)) {
|
||||||
version = "v2";
|
LOG.info("Invalid v3 hostname: " + onion3);
|
||||||
} else {
|
|
||||||
// Don't scrub the address so we can find the problem
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Invalid v2 hostname: " + onion2);
|
|
||||||
}
|
}
|
||||||
|
onion3 = null;
|
||||||
}
|
}
|
||||||
if (!isNullOrEmpty(onion3)) {
|
if (onion3 == null) return null;
|
||||||
if (ONION_V3.matcher(onion3).matches()) {
|
|
||||||
bestOnion = onion3;
|
|
||||||
version = "v3";
|
|
||||||
} else {
|
|
||||||
// Don't scrub the address so we can find the problem
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Invalid v3 hostname: " + onion3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bestOnion == null) return null;
|
|
||||||
Socket s = null;
|
Socket s = null;
|
||||||
try {
|
try {
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Connecting to " + version + " "
|
LOG.info("Connecting to v3 " + scrubOnion(onion3));
|
||||||
+ scrubOnion(bestOnion));
|
|
||||||
}
|
}
|
||||||
s = torSocketFactory.createSocket(bestOnion + ".onion", 80);
|
s = torSocketFactory.createSocket(onion3 + ".onion", 80);
|
||||||
s.setSoTimeout(socketTimeout);
|
s.setSoTimeout(socketTimeout);
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Connected to " + version + " "
|
LOG.info("Connected to v3 " + scrubOnion(onion3));
|
||||||
+ scrubOnion(bestOnion));
|
|
||||||
}
|
}
|
||||||
return new TorTransportConnection(this, s);
|
return new TorTransportConnection(this, s);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info("Could not connect to " + version + " "
|
LOG.info("Could not connect to v3 "
|
||||||
+ scrubOnion(bestOnion) + ": " + e.toString());
|
+ scrubOnion(onion3) + ": " + e);
|
||||||
}
|
}
|
||||||
tryToClose(s, LOG, WARNING);
|
tryToClose(s, LOG, WARNING);
|
||||||
return null;
|
return null;
|
||||||
@@ -747,8 +685,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
byte[] localSeed = alice ? aliceSeed : bobSeed;
|
byte[] localSeed = alice ? aliceSeed : bobSeed;
|
||||||
byte[] remoteSeed = alice ? bobSeed : aliceSeed;
|
byte[] remoteSeed = alice ? bobSeed : aliceSeed;
|
||||||
String blob = torRendezvousCrypto.getPrivateKeyBlob(localSeed);
|
String blob = torRendezvousCrypto.getPrivateKeyBlob(localSeed);
|
||||||
String localOnion = torRendezvousCrypto.getOnionAddress(localSeed);
|
String localOnion = torRendezvousCrypto.getOnion(localSeed);
|
||||||
String remoteOnion = torRendezvousCrypto.getOnionAddress(remoteSeed);
|
String remoteOnion = torRendezvousCrypto.getOnion(remoteSeed);
|
||||||
TransportProperties remoteProperties = new TransportProperties();
|
TransportProperties remoteProperties = new TransportProperties();
|
||||||
remoteProperties.put(PROP_ONION_V3, remoteOnion);
|
remoteProperties.put(PROP_ONION_V3, remoteOnion);
|
||||||
try {
|
try {
|
||||||
@@ -792,9 +730,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void circuitStatus(String status, String id, String path) {
|
public void circuitStatus(String status, String id, String path) {
|
||||||
if (status.equals("BUILT") &&
|
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
|
||||||
state.getAndSetCircuitBuilt()) {
|
// DisableNetwork, set our circuitBuilt flag if not already set
|
||||||
LOG.info("First circuit built");
|
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
|
||||||
|
LOG.info("Circuit built");
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -805,9 +744,16 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orConnStatus(String status, String orName) {
|
public void orConnStatus(String status, String orName) {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||||
LOG.info("OR connection " + status + " " + orName);
|
|
||||||
if (status.equals("CLOSED") || status.equals("FAILED")) {
|
//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
|
// Check whether we've lost connectivity
|
||||||
updateConnectionStatus(networkManager.getNetworkStatus(),
|
updateConnectionStatus(networkManager.getNetworkStatus(),
|
||||||
batteryManager.isCharging());
|
batteryManager.isCharging());
|
||||||
@@ -825,24 +771,81 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void message(String severity, String msg) {
|
public void message(String severity, String msg) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
|
||||||
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
|
if (msg.startsWith("Switching to guard context")) {
|
||||||
state.setBootstrapped();
|
state.onSwitchingGuardContext();
|
||||||
backoff.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unrecognized(String type, String msg) {
|
public void unrecognized(String type, String msg) {
|
||||||
if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
if (type.equals("STATUS_CLIENT")) {
|
||||||
if (LOG.isLoggable(INFO)) {
|
handleClientStatus(removeSeverity(msg));
|
||||||
String[] words = msg.split(" ");
|
} else if (type.equals("STATUS_GENERAL")) {
|
||||||
if (words.length > 1 && ONION_V3.matcher(words[1]).matches()) {
|
handleGeneralStatus(removeSeverity(msg));
|
||||||
LOG.info("V3 descriptor uploaded");
|
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
|
||||||
} else {
|
String[] parts = msg.split(" ");
|
||||||
LOG.info("V2 descriptor uploaded");
|
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
|
@Override
|
||||||
@@ -911,7 +914,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
int reasonsDisabled = 0;
|
int reasonsDisabled = 0;
|
||||||
boolean enableNetwork = false, enableBridges = false;
|
boolean enableNetwork = false, enableBridges = false;
|
||||||
boolean useMeek = false, enableConnectionPadding = false;
|
boolean enableConnectionPadding = false;
|
||||||
|
List<BridgeType> bridgeTypes =
|
||||||
|
circumventionProvider.getSuitableBridgeTypes(country);
|
||||||
|
|
||||||
if (!online) {
|
if (!online) {
|
||||||
LOG.info("Disabling network, device is offline");
|
LOG.info("Disabling network, device is offline");
|
||||||
@@ -940,14 +945,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
enableNetwork = true;
|
enableNetwork = true;
|
||||||
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
|
||||||
(automatic && bridgesWork)) {
|
(automatic && bridgesWork)) {
|
||||||
if (ipv6Only ||
|
if (ipv6Only) bridgeTypes = singletonList(MEEK);
|
||||||
circumventionProvider.needsMeek(country)) {
|
enableBridges = true;
|
||||||
LOG.info("Using meek bridges");
|
if (LOG.isLoggable(INFO)) {
|
||||||
enableBridges = true;
|
LOG.info("Using bridge types " + bridgeTypes);
|
||||||
useMeek = true;
|
|
||||||
} else {
|
|
||||||
LOG.info("Using obfs4 bridges");
|
|
||||||
enableBridges = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Not using bridges");
|
LOG.info("Not using bridges");
|
||||||
@@ -965,7 +966,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (enableNetwork) {
|
if (enableNetwork) {
|
||||||
enableBridges(enableBridges, useMeek);
|
enableBridges(enableBridges, bridgeTypes);
|
||||||
enableConnectionPadding(enableConnectionPadding);
|
enableConnectionPadding(enableConnectionPadding);
|
||||||
useIpv6(ipv6Only);
|
useIpv6(ipv6Only);
|
||||||
}
|
}
|
||||||
@@ -1005,17 +1006,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private ServerSocket serverSocket = null;
|
private ServerSocket serverSocket = null;
|
||||||
|
|
||||||
synchronized void setStarted() {
|
@GuardedBy("this")
|
||||||
|
private int orConnectionsPending = 0, orConnectionsConnected = 0;
|
||||||
|
|
||||||
|
private synchronized void setStarted() {
|
||||||
started = true;
|
started = true;
|
||||||
callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isTorRunning() {
|
private synchronized boolean isTorRunning() {
|
||||||
return started && !stopped;
|
return started && !stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
synchronized ServerSocket setStopped() {
|
private synchronized ServerSocket setStopped() {
|
||||||
stopped = true;
|
stopped = true;
|
||||||
ServerSocket ss = serverSocket;
|
ServerSocket ss = serverSocket;
|
||||||
serverSocket = null;
|
serverSocket = null;
|
||||||
@@ -1023,44 +1027,44 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return ss;
|
return ss;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void setBootstrapped() {
|
private synchronized void setBootstrapped() {
|
||||||
bootstrapped = true;
|
bootstrapped = true;
|
||||||
callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean getAndSetCircuitBuilt() {
|
private synchronized boolean getAndSetCircuitBuilt(boolean built) {
|
||||||
boolean firstCircuit = !circuitBuilt;
|
boolean old = circuitBuilt;
|
||||||
circuitBuilt = true;
|
circuitBuilt = built;
|
||||||
callback.pluginStateChanged(getState());
|
if (built != old) callback.pluginStateChanged(getState());
|
||||||
return firstCircuit;
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void enableNetwork(boolean enable) {
|
private synchronized void enableNetwork(boolean enable) {
|
||||||
networkInitialised = true;
|
networkInitialised = true;
|
||||||
networkEnabled = enable;
|
networkEnabled = enable;
|
||||||
if (!enable) circuitBuilt = false;
|
if (!enable) circuitBuilt = false;
|
||||||
callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void setReasonsDisabled(int reasonsDisabled) {
|
private synchronized void setReasonsDisabled(int reasonsDisabled) {
|
||||||
settingsChecked = true;
|
settingsChecked = true;
|
||||||
this.reasonsDisabled = reasonsDisabled;
|
this.reasonsDisabled = reasonsDisabled;
|
||||||
callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doesn't affect getState()
|
// Doesn't affect getState()
|
||||||
synchronized boolean setServerSocket(ServerSocket ss) {
|
private synchronized boolean setServerSocket(ServerSocket ss) {
|
||||||
if (stopped || serverSocket != null) return false;
|
if (stopped || serverSocket != null) return false;
|
||||||
serverSocket = ss;
|
serverSocket = ss;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doesn't affect getState()
|
// Doesn't affect getState()
|
||||||
synchronized void clearServerSocket(ServerSocket ss) {
|
private synchronized void clearServerSocket(ServerSocket ss) {
|
||||||
if (serverSocket == ss) serverSocket = null;
|
if (serverSocket == ss) serverSocket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized State getState() {
|
private synchronized State getState() {
|
||||||
if (!started || stopped || !settingsChecked) {
|
if (!started || stopped || !settingsChecked) {
|
||||||
return STARTING_STOPPING;
|
return STARTING_STOPPING;
|
||||||
}
|
}
|
||||||
@@ -1070,8 +1074,61 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
|
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int getReasonsDisabled() {
|
private synchronized int getReasonsDisabled() {
|
||||||
return getState() == DISABLED ? reasonsDisabled : 0;
|
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;
|
static final int SEED_BYTES = 32;
|
||||||
|
|
||||||
String getOnionAddress(byte[] seed);
|
String getOnion(byte[] seed);
|
||||||
|
|
||||||
String getPrivateKeyBlob(byte[] seed);
|
String getPrivateKeyBlob(byte[] seed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,39 +4,26 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
|||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.Digest;
|
|
||||||
import org.bouncycastle.crypto.digests.SHA3Digest;
|
|
||||||
import org.bouncycastle.util.encoders.Base64;
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
import org.briarproject.bramble.util.Base32;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
import static java.lang.System.arraycopy;
|
class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
|
||||||
|
|
||||||
public class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
|
|
||||||
|
|
||||||
private static final EdDSANamedCurveSpec CURVE_SPEC =
|
private static final EdDSANamedCurveSpec CURVE_SPEC =
|
||||||
EdDSANamedCurveTable.getByName("Ed25519");
|
EdDSANamedCurveTable.getByName("Ed25519");
|
||||||
|
|
||||||
private static final byte HS_PROTOCOL_VERSION = 3;
|
private final CryptoComponent crypto;
|
||||||
private static final int CHECKSUM_BYTES = 2;
|
|
||||||
|
TorRendezvousCryptoImpl(CryptoComponent crypto) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getOnionAddress(byte[] seed) {
|
public String getOnion(byte[] seed) {
|
||||||
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
|
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
|
||||||
byte[] publicKey = spec.getA().toByteArray();
|
return crypto.encodeOnion(spec.getA().toByteArray());
|
||||||
Digest digest = new SHA3Digest(256);
|
|
||||||
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
|
|
||||||
digest.update(label, 0, label.length);
|
|
||||||
digest.update(publicKey, 0, publicKey.length);
|
|
||||||
digest.update(HS_PROTOCOL_VERSION);
|
|
||||||
byte[] checksum = new byte[digest.getDigestSize()];
|
|
||||||
digest.doFinal(checksum, 0);
|
|
||||||
byte[] address = new byte[publicKey.length + CHECKSUM_BYTES + 1];
|
|
||||||
arraycopy(publicKey, 0, address, 0, publicKey.length);
|
|
||||||
arraycopy(checksum, 0, address, publicKey.length, CHECKSUM_BYTES);
|
|
||||||
address[address.length - 1] = HS_PROTOCOL_VERSION;
|
|
||||||
return Base32.encode(address).toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -37,4 +37,10 @@ class SettingsManagerImpl implements SettingsManager {
|
|||||||
public void mergeSettings(Settings s, String namespace) throws DbException {
|
public void mergeSettings(Settings s, String namespace) throws DbException {
|
||||||
db.transaction(false, txn -> db.mergeSettings(txn, s, namespace));
|
db.transaction(false, txn -> db.mergeSettings(txn, s, namespace));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mergeSettings(Transaction txn, Settings s, String namespace)
|
||||||
|
throws DbException {
|
||||||
|
db.mergeSettings(txn, s, namespace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
|
||||||
|
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_CONNECT_TIMEOUT;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -20,6 +21,6 @@ public class SocksModule {
|
|||||||
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
|
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
|
||||||
torSocksPort);
|
torSocksPort);
|
||||||
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
|
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
|
||||||
EXTRA_SOCKET_TIMEOUT);
|
EXTRA_CONNECT_TIMEOUT, EXTRA_SOCKET_TIMEOUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,15 +26,18 @@ class SocksSocket extends Socket {
|
|||||||
"Address type not supported"
|
"Address type not supported"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("MismatchedReadAndWriteOfArray")
|
||||||
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
||||||
|
|
||||||
private final SocketAddress proxy;
|
private final SocketAddress proxy;
|
||||||
private final int connectToProxyTimeout, extraSocketTimeout;
|
private final int connectToProxyTimeout;
|
||||||
|
private final int extraConnectTimeout, extraSocketTimeout;
|
||||||
|
|
||||||
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
|
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
|
||||||
int extraSocketTimeout) {
|
int extraConnectTimeout, int extraSocketTimeout) {
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.connectToProxyTimeout = connectToProxyTimeout;
|
this.connectToProxyTimeout = connectToProxyTimeout;
|
||||||
|
this.extraConnectTimeout = extraConnectTimeout;
|
||||||
this.extraSocketTimeout = extraSocketTimeout;
|
this.extraSocketTimeout = extraSocketTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ class SocksSocket extends Socket {
|
|||||||
|
|
||||||
// Use the supplied timeout temporarily, plus any configured extra
|
// Use the supplied timeout temporarily, plus any configured extra
|
||||||
int oldTimeout = getSoTimeout();
|
int oldTimeout = getSoTimeout();
|
||||||
setSoTimeout(timeout + extraSocketTimeout);
|
setSoTimeout(timeout + extraConnectTimeout);
|
||||||
|
|
||||||
// Connect to the endpoint via the proxy
|
// Connect to the endpoint via the proxy
|
||||||
sendConnectRequest(out, host, port);
|
sendConnectRequest(out, host, port);
|
||||||
|
|||||||
@@ -11,18 +11,21 @@ import javax.net.SocketFactory;
|
|||||||
class SocksSocketFactory extends SocketFactory {
|
class SocksSocketFactory extends SocketFactory {
|
||||||
|
|
||||||
private final SocketAddress proxy;
|
private final SocketAddress proxy;
|
||||||
private final int connectToProxyTimeout, extraSocketTimeout;
|
private final int connectToProxyTimeout;
|
||||||
|
private final int extraConnectTimeout, extraSocketTimeout;
|
||||||
|
|
||||||
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
|
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
|
||||||
int extraSocketTimeout) {
|
int extraConnectTimeout, int extraSocketTimeout) {
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.connectToProxyTimeout = connectToProxyTimeout;
|
this.connectToProxyTimeout = connectToProxyTimeout;
|
||||||
|
this.extraConnectTimeout = extraConnectTimeout;
|
||||||
this.extraSocketTimeout = extraSocketTimeout;
|
this.extraSocketTimeout = extraSocketTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Socket createSocket() {
|
public Socket createSocket() {
|
||||||
return new SocksSocket(proxy, connectToProxyTimeout, extraSocketTimeout);
|
return new SocksSocket(proxy, connectToProxyTimeout,
|
||||||
|
extraConnectTimeout, extraSocketTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ import java.io.DataOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static java.net.NetworkInterface.getNetworkInterfaces;
|
|
||||||
import static java.util.Collections.list;
|
import static java.util.Collections.list;
|
||||||
|
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -25,14 +24,11 @@ abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
|
|||||||
out.writeLong(System.currentTimeMillis());
|
out.writeLong(System.currentTimeMillis());
|
||||||
out.writeLong(System.nanoTime());
|
out.writeLong(System.nanoTime());
|
||||||
out.writeLong(Runtime.getRuntime().freeMemory());
|
out.writeLong(Runtime.getRuntime().freeMemory());
|
||||||
Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
|
for (NetworkInterface i : getNetworkInterfaces()) {
|
||||||
if (ifaces != null) {
|
for (InetAddress a : list(i.getInetAddresses()))
|
||||||
for (NetworkInterface i : list(ifaces)) {
|
out.write(a.getAddress());
|
||||||
for (InetAddress a : list(i.getInetAddresses()))
|
byte[] hardware = i.getHardwareAddress();
|
||||||
out.write(a.getAddress());
|
if (hardware != null) out.write(hardware);
|
||||||
byte[] hardware = i.getHardwareAddress();
|
|
||||||
if (hardware != null) out.write(hardware);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (Entry<String, String> e : System.getenv().entrySet()) {
|
for (Entry<String, String> e : System.getenv().entrySet()) {
|
||||||
out.writeUTF(e.getKey());
|
out.writeUTF(e.getKey());
|
||||||
|
|||||||
@@ -215,6 +215,23 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
m.getStreamContext(txn, tag)));
|
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
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
if (e instanceof ContactRemovedEvent) {
|
if (e instanceof ContactRemovedEvent) {
|
||||||
|
|||||||
@@ -48,4 +48,9 @@ interface TransportKeyManager {
|
|||||||
StreamContext getStreamContext(Transaction txn, byte[] tag)
|
StreamContext getStreamContext(Transaction txn, byte[] tag)
|
||||||
throws DbException;
|
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 {
|
throws DbException {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
// Look up the incoming keys for the tag
|
StreamContext ctx = streamContextFromTag(tag);
|
||||||
TagContext tagCtx = inContexts.remove(new Bytes(tag));
|
if (ctx == null) return null;
|
||||||
if (tagCtx == null) return null;
|
markTagAsRecognised(txn, tag);
|
||||||
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);
|
|
||||||
}
|
|
||||||
return ctx;
|
return ctx;
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
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
|
@DatabaseExecutor
|
||||||
@Wakeful
|
@Wakeful
|
||||||
private void updateKeys(Transaction txn) throws DbException {
|
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
|
d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
d Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
d Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
|
||||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||||
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||||
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||||
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||||
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
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.api.sync.MessageContext;
|
||||||
import org.briarproject.bramble.test.ValidatorTestCase;
|
import org.briarproject.bramble.test.ValidatorTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
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();
|
private final Metadata meta = new Metadata();
|
||||||
|
|
||||||
public BdfMessageValidatorTest() {
|
public BdfMessageValidatorTest() {
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = InvalidMessageException.class)
|
@Test(expected = InvalidMessageException.class)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.client;
|
package org.briarproject.bramble.client;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
@@ -20,15 +21,16 @@ import org.briarproject.bramble.api.db.Metadata;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
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.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -42,20 +44,26 @@ 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_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_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.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.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
|
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey;
|
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.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class ClientHelperImplTest extends BrambleTestCase {
|
public class ClientHelperImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
private final Mockery context = new Mockery();
|
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
private final MessageFactory messageFactory =
|
private final MessageFactory messageFactory =
|
||||||
context.mock(MessageFactory.class);
|
context.mock(MessageFactory.class);
|
||||||
@@ -80,13 +88,35 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
private final long timestamp = message.getTimestamp();
|
private final long timestamp = message.getTimestamp();
|
||||||
private final Metadata metadata = new Metadata();
|
private final Metadata metadata = new Metadata();
|
||||||
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
|
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 Author author = getAuthor();
|
||||||
|
|
||||||
private final ClientHelper clientHelper = new ClientHelperImpl(db,
|
private final ClientHelper clientHelper = new ClientHelperImpl(db,
|
||||||
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
|
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
|
||||||
metadataEncoder, cryptoComponent, authorFactory);
|
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
|
@Test
|
||||||
public void testAddLocalMessage() throws Exception {
|
public void testAddLocalMessage() throws Exception {
|
||||||
boolean shared = new Random().nextBoolean();
|
boolean shared = new Random().nextBoolean();
|
||||||
@@ -100,7 +130,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.addLocalMessage(message, dictionary, shared);
|
clientHelper.addLocalMessage(message, dictionary, shared);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -112,7 +141,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.createMessage(groupId, timestamp, list);
|
clientHelper.createMessage(groupId, timestamp, list);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -127,7 +155,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.getMessageAsList(messageId);
|
clientHelper.getMessageAsList(messageId);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -144,7 +171,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(dictionary,
|
assertEquals(dictionary,
|
||||||
clientHelper.getGroupMetadataAsDictionary(groupId));
|
clientHelper.getGroupMetadataAsDictionary(groupId));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,7 +187,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(dictionary,
|
assertEquals(dictionary,
|
||||||
clientHelper.getMessageMetadataAsDictionary(messageId));
|
clientHelper.getMessageMetadataAsDictionary(messageId));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -179,7 +204,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
|
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -204,7 +228,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(map,
|
assertEquals(map,
|
||||||
clientHelper.getMessageMetadataAsDictionary(groupId, query));
|
clientHelper.getMessageMetadataAsDictionary(groupId, query));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -219,7 +242,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.mergeGroupMetadata(groupId, dictionary);
|
clientHelper.mergeGroupMetadata(groupId, dictionary);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -234,7 +256,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.mergeMessageMetadata(messageId, dictionary);
|
clientHelper.mergeMessageMetadata(messageId, dictionary);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -242,7 +263,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
byte[] bytes = expectToByteArray(list);
|
byte[] bytes = expectToByteArray(list);
|
||||||
|
|
||||||
assertArrayEquals(bytes, clientHelper.toByteArray(list));
|
assertArrayEquals(bytes, clientHelper.toByteArray(list));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -250,7 +270,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
expectToList(true);
|
expectToList(true);
|
||||||
|
|
||||||
assertEquals(list, clientHelper.toList(getRandomBytes(123)));
|
assertEquals(list, clientHelper.toList(getRandomBytes(123)));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -262,7 +281,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
fail();
|
fail();
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
// expected
|
// expected
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +297,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertArrayEquals(signature,
|
assertArrayEquals(signature,
|
||||||
clientHelper.sign(label, list, privateKey));
|
clientHelper.sign(label, list, privateKey));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -295,7 +312,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.verifySignature(signature, label, list, publicKey);
|
clientHelper.verifySignature(signature, label, list, publicKey);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -315,7 +331,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
fail();
|
fail();
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
// expected
|
// expected
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,4 +545,95 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
will(returnValue(eof));
|
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));
|
will(returnValue(ids));
|
||||||
oneOf(database).getMessage(txn, messageId);
|
oneOf(database).getMessage(txn, messageId);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
oneOf(database).getMessage(txn, messageId1);
|
oneOf(database).getMessage(txn, messageId1);
|
||||||
will(returnValue(message1));
|
will(returnValue(message1));
|
||||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
@@ -951,9 +951,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).getMessagesToOffer(txn, contactId, 123, maxLatency);
|
oneOf(database).getMessagesToOffer(txn, contactId, 123, maxLatency);
|
||||||
will(returnValue(ids));
|
will(returnValue(ids));
|
||||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
}});
|
}});
|
||||||
@@ -1005,12 +1005,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(ids));
|
will(returnValue(ids));
|
||||||
oneOf(database).getMessage(txn, messageId);
|
oneOf(database).getMessage(txn, messageId);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
|
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||||
maxLatency);
|
messageId, maxLatency);
|
||||||
oneOf(database).getMessage(txn, messageId1);
|
oneOf(database).getMessage(txn, messageId1);
|
||||||
will(returnValue(message1));
|
will(returnValue(message1));
|
||||||
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
|
oneOf(database).updateRetransmissionData(txn, contactId,
|
||||||
maxLatency);
|
messageId1, maxLatency);
|
||||||
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
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.INVALID;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
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.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.deleteTestDirectory;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||||
@@ -444,7 +441,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertOneMessageToSendEagerly(db, txn);
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
|
|
||||||
// Mark the message as sent
|
// 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,
|
// The message should no longer be sendable via lazy retransmission,
|
||||||
// but it should still be sendable via eager retransmission
|
// but it should still be sendable via eager retransmission
|
||||||
@@ -1811,7 +1808,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertFalse(status.isSeen());
|
assertFalse(status.isSeen());
|
||||||
|
|
||||||
// Pretend the message was sent to the contact
|
// 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
|
// The message should be sent but not seen
|
||||||
status = db.getMessageStatus(txn, contactId, messageId);
|
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
|
// Update the message's expiry time as though we sent it - now the
|
||||||
// message should be sendable after one round-trip
|
// 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));
|
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
// Update the message's expiry time again - now it should be sendable
|
// Update the message's expiry time again - now it should be sendable
|
||||||
// after two round-trips
|
// after two round-trips
|
||||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, 1000);
|
db.updateRetransmissionData(txn, contactId, messageId, 1000);
|
||||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
// Delete the message - there should be no messages to send
|
// Delete the message - there should be no messages to send
|
||||||
@@ -2124,7 +2122,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Time: now
|
// Time: now
|
||||||
// Mark the message as sent
|
// 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
|
// The message should expire after 2 * MAX_LATENCY
|
||||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||||
@@ -2161,6 +2159,51 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
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
|
// Time: now
|
||||||
// Retrieve the message from the database
|
// Retrieve the message from the database
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
@@ -2169,70 +2212,27 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Time: now
|
// Time: now
|
||||||
// Mark the message as sent
|
// 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
|
// The message should expire after 2 * MAX_LATENCY
|
||||||
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
// Time: now
|
// Time: now + MAX_LATENCY * 2 - 1
|
||||||
// The message should not be sendable via the same transport
|
// The message should not yet be sendable
|
||||||
|
time.set(now + MAX_LATENCY * 2 - 1);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
assertTrue(ids.isEmpty());
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// Time: now
|
// Reset the retransmission times
|
||||||
// The message should be sendable via a transport with a faster ETA
|
db.resetUnackedMessagesToSend(txn, contactId);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
|
||||||
MAX_LATENCY - 1);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
|
|
||||||
// Time: now + 1
|
// The message should have infinitely short expiry
|
||||||
// The message should no longer be sendable via the faster transport,
|
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||||
// as the ETA is now equal
|
|
||||||
time.set(now + 1);
|
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE,
|
|
||||||
MAX_LATENCY - 1);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
// The message should be sendable
|
||||||
db.close();
|
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.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
|||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.auto.Mock;
|
import org.jmock.auto.Mock;
|
||||||
|
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||||
import org.jmock.integration.junit4.JUnitRuleMockery;
|
import org.jmock.integration.junit4.JUnitRuleMockery;
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.lib.concurrent.Synchroniser;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -34,7 +35,8 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
@Rule
|
@Rule
|
||||||
public JUnitRuleMockery context = new JUnitRuleMockery() {{
|
public JUnitRuleMockery context = new JUnitRuleMockery() {{
|
||||||
// So we can mock concrete classes like KeyAgreementTransport
|
// So we can mock concrete classes like KeyAgreementTransport
|
||||||
setImposteriser(ClassImposteriser.INSTANCE);
|
setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||||
|
setThreadingPolicy(new Synchroniser());
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private final PublicKey alicePubKey = getAgreementPublicKey();
|
private final PublicKey alicePubKey = getAgreementPublicKey();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.record.RecordWriterFactory;
|
|||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -58,7 +58,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
|||||||
private KeyAgreementTransport kat;
|
private KeyAgreementTransport kat;
|
||||||
|
|
||||||
public KeyAgreementTransportTest() {
|
public KeyAgreementTransportTest() {
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||||
inputStream = context.mock(InputStream.class);
|
inputStream = context.mock(InputStream.class);
|
||||||
outputStream = context.mock(OutputStream.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,701 @@
|
|||||||
|
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);
|
||||||
|
Contact contact = getContact();
|
||||||
|
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);
|
||||||
|
will(returnValue(contact.getId()));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||||
|
will(returnValue(body));
|
||||||
|
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||||
|
propsDict);
|
||||||
|
will(returnValue(props));
|
||||||
|
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
||||||
|
}});
|
||||||
|
|
||||||
|
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);
|
||||||
|
Contact contact = getContact();
|
||||||
|
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);
|
||||||
|
will(returnValue(contact.getId()));
|
||||||
|
oneOf(clientHelper).getMessageAsList(txn, message.getId());
|
||||||
|
will(returnValue(body));
|
||||||
|
oneOf(clientHelper).parseAndValidateMailboxPropertiesUpdate(
|
||||||
|
propsDict);
|
||||||
|
will(returnValue(props));
|
||||||
|
oneOf(db).resetUnackedMessagesToSend(txn, contact.getId());
|
||||||
|
}});
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user