mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
113 Commits
checkstyle
...
beta-1.4.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36670a8bf6 | ||
|
|
32d62f9960 | ||
|
|
eafd6a1ca1 | ||
|
|
1614e72c43 | ||
|
|
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,43 @@ 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
|
||||||
|
|
||||||
|
mailbox integration test:
|
||||||
|
extends: .optional_tests
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
|
when: on_success
|
||||||
|
allow_failure: false
|
||||||
|
- if: '$CI_COMMIT_TAG == null'
|
||||||
|
when: manual
|
||||||
|
allow_failure: false
|
||||||
|
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
|
||||||
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 10404
|
||||||
versionName "1.4.1"
|
versionName "1.4.4"
|
||||||
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@zip"
|
||||||
|
|
||||||
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'
|
||||||
|
|||||||
@@ -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.**
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
|
||||||
'org.briarproject:tor-android:0.3.5.15:tor-android-0.3.5.15.jar:560c5070166300b396cb2f28d82d9f639ee1fb5479096a3cef67da56d39937ad',
|
'org.briarproject:tor-android:0.3.5.17:tor-android-0.3.5.17.jar:1888afc10a26b93d00a010ea27bf0b1b162a6d524688b08b98d70d14dc363b54',
|
||||||
'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',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ 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"
|
|
||||||
|
|
||||||
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,10 @@ public interface FeatureFlags {
|
|||||||
boolean shouldEnableProfilePictures();
|
boolean shouldEnableProfilePictures();
|
||||||
|
|
||||||
boolean shouldEnableDisappearingMessages();
|
boolean shouldEnableDisappearingMessages();
|
||||||
|
|
||||||
|
boolean shouldEnablePrivateGroupsInCore();
|
||||||
|
|
||||||
|
boolean shouldEnableForumsInCore();
|
||||||
|
|
||||||
|
boolean shouldEnableBlogsInCore();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -128,7 +128,7 @@ public interface ClientHelper {
|
|||||||
* 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,6 +166,13 @@ 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}.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -170,4 +170,11 @@ 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/hidden service address given its public key. As
|
||||||
|
* specified here: https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
|
||||||
|
*/
|
||||||
|
String encodeOnionAddress(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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the transmission count, expiry time and ETA 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
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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 onionAddress, authToken;
|
||||||
|
private final boolean owner;
|
||||||
|
|
||||||
|
public MailboxProperties(String onionAddress, String authToken,
|
||||||
|
boolean owner) {
|
||||||
|
this.onionAddress = onionAddress;
|
||||||
|
this.authToken = authToken;
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOnionAddress() {
|
||||||
|
return onionAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthToken() {
|
||||||
|
return authToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOwner() {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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 javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface MailboxSettingsManager {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
package org.briarproject.bramble.api.plugin;
|
package org.briarproject.bramble.api.plugin;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -21,14 +18,7 @@ public interface TorConstants {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@@ -46,6 +47,7 @@ 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.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
@@ -209,6 +211,10 @@ public class TestUtils {
|
|||||||
getAgreementPublicKey(), verified);
|
getAgreementPublicKey(), verified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getMailboxSecret() {
|
||||||
|
return toHexString(getRandomBytes(32)).toLowerCase(Locale.US);
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -121,28 +121,39 @@ 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 +165,13 @@ 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) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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.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 +22,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;
|
||||||
@@ -58,6 +61,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
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;
|
||||||
@@ -442,4 +447,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 encodeOnionAddress(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,13 @@ 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 ETA 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.
|
||||||
@@ -845,8 +852,8 @@ interface Database<T> {
|
|||||||
* of the given message with respect to the given contact, using the latency
|
* of the given message with respect to the given contact, using the latency
|
||||||
* of the transport over which it was sent.
|
* of the transport over which it was sent.
|
||||||
*/
|
*/
|
||||||
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, long maxLatency)
|
void updateExpiryTimeAndEta(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.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -429,8 +429,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();
|
||||||
|
try {
|
||||||
closed = false;
|
closed = false;
|
||||||
|
} finally {
|
||||||
|
connectionsLock.unlock();
|
||||||
}
|
}
|
||||||
txn = startTransaction();
|
txn = startTransaction();
|
||||||
try {
|
try {
|
||||||
@@ -3290,6 +3293,29 @@ 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, eta = 0"
|
||||||
|
+ " 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 {
|
||||||
|
|||||||
@@ -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,88 @@
|
|||||||
|
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.MailboxProperties;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
String 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@JsonSerialize
|
||||||
|
class MailboxContact {
|
||||||
|
public final int contactId;
|
||||||
|
public final String token, inboxId, outboxId;
|
||||||
|
|
||||||
|
MailboxContact(ContactId contactId,
|
||||||
|
String token,
|
||||||
|
String inboxId,
|
||||||
|
String outboxId) {
|
||||||
|
this.contactId = contactId.getInt();
|
||||||
|
this.token = token;
|
||||||
|
this.inboxId = inboxId;
|
||||||
|
this.outboxId = outboxId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class ApiException extends Exception {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,172 @@
|
|||||||
|
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 org.briarproject.bramble.api.WeakSingletonProvider;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
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.StringUtils.fromHexString;
|
||||||
|
|
||||||
|
@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"));
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
|
||||||
|
this.httpClientProvider = httpClientProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setup(MailboxProperties properties)
|
||||||
|
throws IOException, ApiException {
|
||||||
|
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||||
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
|
.url(properties.getOnionAddress() + "/setup")
|
||||||
|
.put(EMPTY_REQUEST)
|
||||||
|
.build();
|
||||||
|
OkHttpClient client = httpClientProvider.get();
|
||||||
|
Response response = client.newCall(request).execute();
|
||||||
|
// TODO consider throwing a special exception for the 401 case
|
||||||
|
if (response.code() == 401) throw new ApiException();
|
||||||
|
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();
|
||||||
|
if (ownerToken == null || !isValidToken(ownerToken)) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
return ownerToken;
|
||||||
|
} catch (JacksonException e) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidToken(String token) {
|
||||||
|
if (token.length() != 64) return false;
|
||||||
|
try {
|
||||||
|
// try to convert to bytes
|
||||||
|
fromHexString(token);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 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);
|
||||||
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
|
.url(properties.getOnionAddress() + "/contacts")
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
OkHttpClient client = httpClientProvider.get();
|
||||||
|
Response response = client.newCall(request).execute();
|
||||||
|
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.getOnionAddress() + "/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());
|
||||||
|
JsonNode contactsNode = node.get("contacts");
|
||||||
|
if (contactsNode == null || !contactsNode.isArray()) {
|
||||||
|
throw new ApiException();
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response sendGetRequest(MailboxProperties properties, String path)
|
||||||
|
throws IOException {
|
||||||
|
Request request = getRequestBuilder(properties.getAuthToken())
|
||||||
|
.url(properties.getOnionAddress() + path)
|
||||||
|
.build();
|
||||||
|
OkHttpClient client = httpClientProvider.get();
|
||||||
|
return client.newCall(request).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Request.Builder getRequestBuilder(String token) {
|
||||||
|
return new Request.Builder()
|
||||||
|
.addHeader("Authorization", "Bearer " + token);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class MailboxModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
MailboxSettingsManager provideMailboxSettingsManager(
|
||||||
|
MailboxSettingsManagerImpl mailboxSettingsManager) {
|
||||||
|
return mailboxSettingsManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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.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.settings.Settings;
|
||||||
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxSettingsManagerImpl(SettingsManager settingsManager) {
|
||||||
|
this.settingsManager = settingsManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
return new MailboxProperties(onion, token, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
|
||||||
|
throws DbException {
|
||||||
|
Settings s = new Settings();
|
||||||
|
s.put(SETTINGS_KEY_ONION, p.getOnionAddress());
|
||||||
|
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken());
|
||||||
|
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recordFailedConnectionAttempt(Transaction txn, long now)
|
||||||
|
throws DbException {
|
||||||
|
Settings oldSettings =
|
||||||
|
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
int attempts = oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
|
Settings newSettings = new Settings();
|
||||||
|
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
|
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts + 1);
|
||||||
|
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +1,71 @@
|
|||||||
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,
|
||||||
|
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 = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Countries where obfs4 or meek bridge connections are likely to work.
|
* Countries where obfs4 or meek 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_OBFS4_BRIDGES}, {@link #NON_DEFAULT_OBFS4_BRIDGES} and
|
||||||
|
* {@link #MEEK_BRIDGES}.
|
||||||
*/
|
*/
|
||||||
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };
|
String[] BRIDGES = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countries where default obfs4 bridges are likely to work.
|
||||||
|
* Should be a subset of {@link #BRIDGES}.
|
||||||
|
*/
|
||||||
|
String[] DEFAULT_OBFS4_BRIDGES = {"EG", "BY", "TR", "SY", "VE"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countries where non-default obfs4 bridges are likely to work.
|
||||||
|
* Should be a subset of {@link #BRIDGES}.
|
||||||
|
*/
|
||||||
|
String[] NON_DEFAULT_OBFS4_BRIDGES = {"RU"};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Countries where obfs4 bridges won't work and meek is needed.
|
* Countries where obfs4 bridges won't work and meek is needed.
|
||||||
* Should be a subset of {@link #BRIDGES}.
|
* Should be a subset of {@link #BRIDGES}.
|
||||||
*/
|
*/
|
||||||
String[] NEEDS_MEEK = {"CN", "IR"};
|
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 best type of bridge connection for the given country, or
|
||||||
|
* {@link #DEFAULT_OBFS4_BRIDGES} if no bridge type is known to work.
|
||||||
|
*/
|
||||||
|
BridgeType getBestBridgeType(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,31 @@ 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 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;
|
||||||
|
|
||||||
|
@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_OBFS4_BRIDGES));
|
||||||
|
private static final Set<String> NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES =
|
||||||
@Nullable
|
new HashSet<>(asList(NON_DEFAULT_OBFS4_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 +47,42 @@ 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 BridgeType getBestBridgeType(String countryCode) {
|
||||||
return BRIDGES_NEED_MEEK.contains(countryCode);
|
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||||
|
return DEFAULT_OBFS4;
|
||||||
|
} else if (NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
|
||||||
|
return NON_DEFAULT_OBFS4;
|
||||||
|
} else if (MEEK_COUNTRIES.contains(countryCode)) {
|
||||||
|
return MEEK;
|
||||||
|
} else {
|
||||||
|
return DEFAULT_OBFS4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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");
|
boolean isDefaultObfs4 = line.startsWith("d ");
|
||||||
if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue;
|
boolean isNonDefaultObfs4 = line.startsWith("n ");
|
||||||
if (!line.startsWith("#")) bridges.add(line);
|
boolean isMeek = line.startsWith("m ");
|
||||||
|
if ((type == DEFAULT_OBFS4 && isDefaultObfs4) ||
|
||||||
|
(type == NON_DEFAULT_OBFS4 && isNonDefaultObfs4) ||
|
||||||
|
(type == MEEK && isMeek)) {
|
||||||
|
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,9 +80,7 @@ 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;
|
||||||
@@ -90,12 +89,11 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_
|
|||||||
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.MEEK;
|
||||||
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;
|
||||||
@@ -115,7 +113,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
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 +132,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);
|
||||||
|
|
||||||
@@ -284,6 +281,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");
|
||||||
@@ -412,6 +410,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 +477,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 +517,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 +544,20 @@ 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, BridgeType bridgeType)
|
||||||
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 (bridgeType == MEEK) {
|
||||||
conf.add("ClientTransportPlugin meek_lite exec " +
|
conf.add("ClientTransportPlugin meek_lite exec " +
|
||||||
obfs4File.getAbsolutePath());
|
obfs4File.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
conf.add("ClientTransportPlugin obfs4 exec " +
|
conf.add("ClientTransportPlugin obfs4 exec " +
|
||||||
obfs4File.getAbsolutePath());
|
obfs4File.getAbsolutePath());
|
||||||
}
|
}
|
||||||
conf.addAll(circumventionProvider.getBridges(needsMeek));
|
conf.addAll(circumventionProvider.getBridges(bridgeType));
|
||||||
controlConnection.setConf(conf);
|
controlConnection.setConf(conf);
|
||||||
} else {
|
} else {
|
||||||
controlConnection.setConf("UseBridges", "0");
|
controlConnection.setConf("UseBridges", "0");
|
||||||
@@ -669,49 +623,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.toString());
|
||||||
}
|
}
|
||||||
tryToClose(s, LOG, WARNING);
|
tryToClose(s, LOG, WARNING);
|
||||||
return null;
|
return null;
|
||||||
@@ -911,7 +846,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;
|
||||||
|
BridgeType bridgeType =
|
||||||
|
circumventionProvider.getBestBridgeType(country);
|
||||||
|
|
||||||
if (!online) {
|
if (!online) {
|
||||||
LOG.info("Disabling network, device is offline");
|
LOG.info("Disabling network, device is offline");
|
||||||
@@ -940,14 +877,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) bridgeType = MEEK;
|
||||||
circumventionProvider.needsMeek(country)) {
|
enableBridges = true;
|
||||||
LOG.info("Using meek bridges");
|
if (LOG.isLoggable(INFO)) {
|
||||||
enableBridges = true;
|
LOG.info("Using bridge type " + bridgeType);
|
||||||
useMeek = true;
|
|
||||||
} else {
|
|
||||||
LOG.info("Using obfs4 bridges");
|
|
||||||
enableBridges = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Not using bridges");
|
LOG.info("Not using bridges");
|
||||||
@@ -965,7 +898,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (enableNetwork) {
|
if (enableNetwork) {
|
||||||
enableBridges(enableBridges, useMeek);
|
enableBridges(enableBridges, bridgeType);
|
||||||
enableConnectionPadding(enableConnectionPadding);
|
enableConnectionPadding(enableConnectionPadding);
|
||||||
useIpv6(ipv6Only);
|
useIpv6(ipv6Only);
|
||||||
}
|
}
|
||||||
@@ -1005,17 +938,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private ServerSocket serverSocket = null;
|
private ServerSocket serverSocket = null;
|
||||||
|
|
||||||
synchronized void setStarted() {
|
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 +956,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 firstCircuit = !circuitBuilt;
|
boolean firstCircuit = !circuitBuilt;
|
||||||
circuitBuilt = true;
|
circuitBuilt = true;
|
||||||
callback.pluginStateChanged(getState());
|
callback.pluginStateChanged(getState());
|
||||||
return firstCircuit;
|
return firstCircuit;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,7 +1003,7 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 getOnionAddress(byte[] seed) {
|
||||||
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
|
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
|
||||||
byte[] publicKey = spec.getA().toByteArray();
|
return crypto.encodeOnionAddress(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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,17 @@
|
|||||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
d Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg 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:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||||
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||||
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||||
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
|
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||||
|
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||||
|
d Bridge obfs4 185.100.85.3:443 5B403DFE34F4872EB027059CECAE30B0C864B3A2 cert=bWUdFUe8io9U6JkSLoGAvSAUDcB779/shovCYmYAQb/pW/iEAMZtO/lCd94OokOF909TPA iat-mode=2
|
||||||
|
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
|
||||||
|
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
|
||||||
|
n Bridge obfs4 23.88.49.56:443 1CDA1660823AE2565D7F50DE8EB99DFDDE96074B cert=4bwNXedHutVD0ZqCm6ph90Vik9dRY4n9qnBHiLiqQOSsIvui4iHwuMFQK6oqiK8tyhVcDw iat-mode=0
|
||||||
|
n Bridge obfs4 185.65.206.101:443 8A3E001D4C5105ED41060597DEEB21FF19CDC4D3 cert=Nd6XZ+f00sGKL1u6USmyvfqR34HN/pt7jEVbgMpXPF/yyGaLBiXRH/x0SIjX5TceYnd+Dg iat-mode=0
|
||||||
|
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)
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ 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.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;
|
||||||
@@ -53,9 +52,8 @@ import static org.junit.Assert.assertArrayEquals;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
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);
|
||||||
@@ -100,7 +98,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.addLocalMessage(message, dictionary, shared);
|
clientHelper.addLocalMessage(message, dictionary, shared);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -112,7 +109,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.createMessage(groupId, timestamp, list);
|
clientHelper.createMessage(groupId, timestamp, list);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -127,7 +123,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.getMessageAsList(messageId);
|
clientHelper.getMessageAsList(messageId);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -144,7 +139,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(dictionary,
|
assertEquals(dictionary,
|
||||||
clientHelper.getGroupMetadataAsDictionary(groupId));
|
clientHelper.getGroupMetadataAsDictionary(groupId));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,7 +155,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(dictionary,
|
assertEquals(dictionary,
|
||||||
clientHelper.getMessageMetadataAsDictionary(messageId));
|
clientHelper.getMessageMetadataAsDictionary(messageId));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -179,7 +172,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
|
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -204,7 +196,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
|
|
||||||
assertEquals(map,
|
assertEquals(map,
|
||||||
clientHelper.getMessageMetadataAsDictionary(groupId, query));
|
clientHelper.getMessageMetadataAsDictionary(groupId, query));
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -219,7 +210,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.mergeGroupMetadata(groupId, dictionary);
|
clientHelper.mergeGroupMetadata(groupId, dictionary);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -234,7 +224,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.mergeMessageMetadata(messageId, dictionary);
|
clientHelper.mergeMessageMetadata(messageId, dictionary);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -242,7 +231,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 +238,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 +249,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
fail();
|
fail();
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
// expected
|
// expected
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +265,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 +280,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.verifySignature(signature, label, list, publicKey);
|
clientHelper.verifySignature(signature, label, list, publicKey);
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -315,7 +299,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
fail();
|
fail();
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
// expected
|
// expected
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2197,6 +2197,55 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResetRetransmissionTimes() throws Exception {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
AtomicLong time = new AtomicLong(now);
|
||||||
|
Database<Connection> db =
|
||||||
|
open(false, new TestMessageFactory(), new SettableClock(time));
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add a contact, a shared group and a shared message
|
||||||
|
db.addIdentity(txn, identity);
|
||||||
|
assertEquals(contactId,
|
||||||
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
|
db.addGroup(txn, group);
|
||||||
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
|
// Time: now
|
||||||
|
// Retrieve the message from the database
|
||||||
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
|
ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertEquals(singletonList(messageId), ids);
|
||||||
|
|
||||||
|
// Time: now
|
||||||
|
// Mark the message as sent
|
||||||
|
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||||
|
|
||||||
|
// The message should expire after 2 * MAX_LATENCY
|
||||||
|
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// Time: now + MAX_LATENCY * 2 - 1
|
||||||
|
// The message should not yet be sendable
|
||||||
|
time.set(now + MAX_LATENCY * 2 - 1);
|
||||||
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
|
// Reset the retransmission times
|
||||||
|
db.resetUnackedMessagesToSend(txn, contactId);
|
||||||
|
|
||||||
|
// The message should have infinitely short expiry
|
||||||
|
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
|
// The message should be sendable
|
||||||
|
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertFalse(ids.isEmpty());
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCompactionTime() throws Exception {
|
public void testCompactionTime() throws Exception {
|
||||||
MessageFactory messageFactory = new TestMessageFactory();
|
MessageFactory messageFactory = new TestMessageFactory();
|
||||||
|
|||||||
@@ -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,383 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
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.TolerableFailureException;
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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 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.getMailboxSecret;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
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 String token = getMailboxSecret();
|
||||||
|
private final String token2 = getMailboxSecret();
|
||||||
|
private final ContactId contactId = getContactId();
|
||||||
|
private final String contactToken = getMailboxSecret();
|
||||||
|
private final String contactInboxId = getMailboxSecret();
|
||||||
|
private final String contactOutboxId = getMailboxSecret();
|
||||||
|
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 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBaseUrl(MockWebServer server) {
|
||||||
|
String baseUrl = server.url("").toString();
|
||||||
|
return baseUrl.substring(0, baseUrl.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertToken(RecordedRequest request, String token) {
|
||||||
|
assertNotNull(request.getHeader("Authorization"));
|
||||||
|
assertEquals("Bearer " + token, request.getHeader("Authorization"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.WeakSingletonProvider;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
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.TolerableFailureException;
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
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.getMailboxSecret;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private final static String URL_BASE = "http://127.0.0.1:8000";
|
||||||
|
private final static String SETUP_TOKEN =
|
||||||
|
"54686973206973206120736574757020746f6b656e20666f722042726961722e";
|
||||||
|
|
||||||
|
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);
|
||||||
|
// 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);
|
||||||
|
String ownerToken = api.setup(setupProperties);
|
||||||
|
ownerProperties = new MailboxProperties(URL_BASE, ownerToken, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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));
|
||||||
|
}
|
||||||
|
|
||||||
|
private MailboxContact getMailboxContact(ContactId contactId) {
|
||||||
|
return new MailboxContact(contactId, getMailboxSecret(),
|
||||||
|
getMailboxSecret(), getMailboxSecret());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package org.briarproject.bramble.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
|
||||||
|
import org.briarproject.bramble.api.mailbox.MailboxStatus;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
|
||||||
|
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final SettingsManager settingsManager =
|
||||||
|
context.mock(SettingsManager.class);
|
||||||
|
|
||||||
|
private final MailboxSettingsManager manager =
|
||||||
|
new MailboxSettingsManagerImpl(settingsManager);
|
||||||
|
private final Random random = new Random();
|
||||||
|
private final String onion = getRandomString(64);
|
||||||
|
private final String token = getRandomString(64);
|
||||||
|
private final ContactId contactId1 = new ContactId(random.nextInt());
|
||||||
|
private final ContactId contactId2 = new ContactId(random.nextInt());
|
||||||
|
private final ContactId contactId3 = new ContactId(random.nextInt());
|
||||||
|
private final long now = System.currentTimeMillis();
|
||||||
|
private final long lastAttempt = now - 1234;
|
||||||
|
private final long lastSuccess = now - 2345;
|
||||||
|
private final int attempts = 123;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsNullPropertiesIfSettingsAreEmpty() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
Settings emptySettings = new Settings();
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
will(returnValue(emptySettings));
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertNull(manager.getOwnMailboxProperties(txn));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsProperties() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
Settings settings = new Settings();
|
||||||
|
settings.put(SETTINGS_KEY_ONION, onion);
|
||||||
|
settings.put(SETTINGS_KEY_TOKEN, token);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
will(returnValue(settings));
|
||||||
|
}});
|
||||||
|
|
||||||
|
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
|
||||||
|
assertNotNull(properties);
|
||||||
|
assertEquals(onion, properties.getOnionAddress());
|
||||||
|
assertEquals(token, properties.getAuthToken());
|
||||||
|
assertTrue(properties.isOwner());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoresProperties() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
Settings expectedSettings = new Settings();
|
||||||
|
expectedSettings.put(SETTINGS_KEY_ONION, onion);
|
||||||
|
expectedSettings.put(SETTINGS_KEY_TOKEN, token);
|
||||||
|
MailboxProperties properties =
|
||||||
|
new MailboxProperties(onion, token, true);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
|
SETTINGS_NAMESPACE);
|
||||||
|
}});
|
||||||
|
|
||||||
|
manager.setOwnMailboxProperties(txn, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsDefaultStatusIfSettingsAreEmpty() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
Settings emptySettings = new Settings();
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
will(returnValue(emptySettings));
|
||||||
|
}});
|
||||||
|
|
||||||
|
MailboxStatus status = manager.getOwnMailboxStatus(txn);
|
||||||
|
assertEquals(-1, status.getTimeOfLastAttempt());
|
||||||
|
assertEquals(-1, status.getTimeOfLastSuccess());
|
||||||
|
assertEquals(0, status.getAttemptsSinceSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReturnsStatus() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
Settings settings = new Settings();
|
||||||
|
settings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt);
|
||||||
|
settings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess);
|
||||||
|
settings.putInt(SETTINGS_KEY_ATTEMPTS, attempts);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
will(returnValue(settings));
|
||||||
|
}});
|
||||||
|
|
||||||
|
MailboxStatus status = manager.getOwnMailboxStatus(txn);
|
||||||
|
assertEquals(lastAttempt, status.getTimeOfLastAttempt());
|
||||||
|
assertEquals(lastSuccess, status.getTimeOfLastSuccess());
|
||||||
|
assertEquals(attempts, status.getAttemptsSinceSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecordsSuccess() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
Settings expectedSettings = new Settings();
|
||||||
|
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
|
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
|
||||||
|
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
|
SETTINGS_NAMESPACE);
|
||||||
|
}});
|
||||||
|
|
||||||
|
manager.recordSuccessfulConnection(txn, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecordsFailureOnFirstAttempt() throws Exception {
|
||||||
|
testRecordsFailure(new Settings(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecordsFailureOnLaterAttempt() throws Exception {
|
||||||
|
Settings oldSettings = new Settings();
|
||||||
|
oldSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt);
|
||||||
|
oldSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess);
|
||||||
|
oldSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts);
|
||||||
|
testRecordsFailure(oldSettings, attempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testRecordsFailure(Settings oldSettings, int oldAttempts)
|
||||||
|
throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
Settings expectedSettings = new Settings();
|
||||||
|
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
|
||||||
|
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, oldAttempts + 1);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
|
||||||
|
will(returnValue(oldSettings));
|
||||||
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
|
||||||
|
SETTINGS_NAMESPACE);
|
||||||
|
}});
|
||||||
|
|
||||||
|
manager.recordFailedConnectionAttempt(txn, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGettingPendingUploads() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
Settings settings = new Settings();
|
||||||
|
settings.put(String.valueOf(contactId1.getInt()), onion);
|
||||||
|
settings.put(String.valueOf(contactId2.getInt()), token);
|
||||||
|
settings.put(String.valueOf(contactId3.getInt()), "");
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
exactly(4).of(settingsManager)
|
||||||
|
.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
|
||||||
|
will(returnValue(settings));
|
||||||
|
}});
|
||||||
|
|
||||||
|
String filename1 = manager.getPendingUpload(txn, contactId1);
|
||||||
|
assertEquals(onion, filename1);
|
||||||
|
String filename2 = manager.getPendingUpload(txn, contactId2);
|
||||||
|
assertEquals(token, filename2);
|
||||||
|
String filename3 = manager.getPendingUpload(txn, contactId3);
|
||||||
|
assertNull(filename3);
|
||||||
|
String filename4 =
|
||||||
|
manager.getPendingUpload(txn, new ContactId(random.nextInt()));
|
||||||
|
assertNull(filename4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSettingPendingUploads() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
// setting a pending upload stores expected settings
|
||||||
|
Settings expectedSettings1 = new Settings();
|
||||||
|
expectedSettings1.put(String.valueOf(contactId1.getInt()), onion);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings1,
|
||||||
|
SETTINGS_UPLOADS_NAMESPACE);
|
||||||
|
}});
|
||||||
|
manager.setPendingUpload(txn, contactId1, onion);
|
||||||
|
|
||||||
|
// nulling a pending upload empties stored settings
|
||||||
|
Settings expectedSettings2 = new Settings();
|
||||||
|
expectedSettings2.put(String.valueOf(contactId2.getInt()), "");
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(settingsManager).mergeSettings(txn, expectedSettings2,
|
||||||
|
SETTINGS_UPLOADS_NAMESPACE);
|
||||||
|
}});
|
||||||
|
manager.setPendingUpload(txn, contactId2, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,10 +12,8 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.jmock.lib.concurrent.Synchroniser;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -24,13 +22,10 @@ import java.util.concurrent.Executors;
|
|||||||
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
|
||||||
public class PluginManagerImplTest extends BrambleTestCase {
|
public class PluginManagerImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartAndStop() throws Exception {
|
public void testStartAndStop() throws Exception {
|
||||||
Mockery context = new Mockery() {{
|
|
||||||
setThreadingPolicy(new Synchroniser());
|
|
||||||
}};
|
|
||||||
Executor ioExecutor = Executors.newSingleThreadExecutor();
|
Executor ioExecutor = Executors.newSingleThreadExecutor();
|
||||||
EventBus eventBus = context.mock(EventBus.class);
|
EventBus eventBus = context.mock(EventBus.class);
|
||||||
PluginConfig pluginConfig = context.mock(PluginConfig.class);
|
PluginConfig pluginConfig = context.mock(PluginConfig.class);
|
||||||
@@ -116,7 +111,5 @@ public class PluginManagerImplTest extends BrambleTestCase {
|
|||||||
// Two plugins should be started and stopped
|
// Two plugins should be started and stopped
|
||||||
p.startService();
|
p.startService();
|
||||||
p.stopService();
|
p.stopService();
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
|
|||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
import org.briarproject.bramble.test.RunAction;
|
import org.briarproject.bramble.test.RunAction;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.imposters.ByteBuddyClassImposteriser;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ public class PollerImplTest extends BrambleMockTestCase {
|
|||||||
private PollerImpl poller;
|
private PollerImpl poller;
|
||||||
|
|
||||||
public PollerImplTest() {
|
public PollerImplTest() {
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
|
||||||
random = context.mock(SecureRandom.class);
|
random = context.mock(SecureRandom.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_OBFS4_BRIDGES;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.MEEK_BRIDGES;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_OBFS4_BRIDGES;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class CircumventionProviderTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
private final CircumventionProvider provider =
|
||||||
|
new CircumventionProviderImpl();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvariants() {
|
||||||
|
Set<String> blocked = new HashSet<>(asList(BLOCKED));
|
||||||
|
Set<String> bridges = new HashSet<>(asList(BRIDGES));
|
||||||
|
Set<String> defaultObfs4Bridges =
|
||||||
|
new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES));
|
||||||
|
Set<String> nonDefaultObfs4Bridges =
|
||||||
|
new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES));
|
||||||
|
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
|
||||||
|
// BRIDGES should be a subset of BLOCKED
|
||||||
|
assertTrue(blocked.containsAll(bridges));
|
||||||
|
// BRIDGES should be the union of the bridge type sets
|
||||||
|
Set<String> union = new HashSet<>(defaultObfs4Bridges);
|
||||||
|
union.addAll(nonDefaultObfs4Bridges);
|
||||||
|
union.addAll(meekBridges);
|
||||||
|
assertEquals(bridges, union);
|
||||||
|
// The bridge type sets should not overlap
|
||||||
|
assertEmptyIntersection(defaultObfs4Bridges, nonDefaultObfs4Bridges);
|
||||||
|
assertEmptyIntersection(defaultObfs4Bridges, meekBridges);
|
||||||
|
assertEmptyIntersection(nonDefaultObfs4Bridges, meekBridges);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetBestBridgeType() {
|
||||||
|
for (String country : DEFAULT_OBFS4_BRIDGES) {
|
||||||
|
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType(country));
|
||||||
|
}
|
||||||
|
for (String country : NON_DEFAULT_OBFS4_BRIDGES) {
|
||||||
|
assertEquals(NON_DEFAULT_OBFS4,
|
||||||
|
provider.getBestBridgeType(country));
|
||||||
|
}
|
||||||
|
for (String country : MEEK_BRIDGES) {
|
||||||
|
assertEquals(MEEK, provider.getBestBridgeType(country));
|
||||||
|
}
|
||||||
|
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType("ZZ"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
|
||||||
|
Set<T> intersection = new HashSet<>(a);
|
||||||
|
intersection.retainAll(b);
|
||||||
|
assertTrue(intersection.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,21 @@ public class TestFeatureFlagModule {
|
|||||||
public boolean shouldEnableDisappearingMessages() {
|
public boolean shouldEnableDisappearingMessages() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnablePrivateGroupsInCore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableForumsInCore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableBlogsInCore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
package org.briarproject.bramble.transport;
|
package org.briarproject.bramble.transport;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.StreamDecrypter;
|
import org.briarproject.bramble.api.crypto.StreamDecrypter;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class StreamReaderImplTest extends BrambleTestCase {
|
public class StreamReaderImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyFramesAreSkipped() throws Exception {
|
public void testEmptyFramesAreSkipped() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
||||||
@@ -30,13 +28,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
|
|||||||
assertEquals(0, r.read()); // Read another byte
|
assertEquals(0, r.read()); // Read another byte
|
||||||
assertEquals(-1, r.read()); // Skip the second empty frame, reach EOF
|
assertEquals(-1, r.read()); // Skip the second empty frame, reach EOF
|
||||||
assertEquals(-1, r.read()); // Still at EOF
|
assertEquals(-1, r.read()); // Still at EOF
|
||||||
context.assertIsSatisfied();
|
|
||||||
r.close();
|
r.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyFramesAreSkippedWithBuffer() throws Exception {
|
public void testEmptyFramesAreSkippedWithBuffer() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
||||||
@@ -56,13 +52,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
|
|||||||
assertEquals(-1, r.read(buf));
|
assertEquals(-1, r.read(buf));
|
||||||
// Still at EOF
|
// Still at EOF
|
||||||
assertEquals(-1, r.read(buf));
|
assertEquals(-1, r.read(buf));
|
||||||
context.assertIsSatisfied();
|
|
||||||
r.close();
|
r.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultipleReadsPerFrame() throws Exception {
|
public void testMultipleReadsPerFrame() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
||||||
@@ -78,13 +72,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
|
|||||||
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf));
|
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf));
|
||||||
// Reach EOF
|
// Reach EOF
|
||||||
assertEquals(-1, r.read(buf, 0, buf.length));
|
assertEquals(-1, r.read(buf, 0, buf.length));
|
||||||
context.assertIsSatisfied();
|
|
||||||
r.close();
|
r.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultipleReadsPerFrameWithOffsets() throws Exception {
|
public void testMultipleReadsPerFrameWithOffsets() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
oneOf(decrypter).readFrame(with(any(byte[].class)));
|
||||||
@@ -102,7 +94,6 @@ public class StreamReaderImplTest extends BrambleTestCase {
|
|||||||
MAX_PAYLOAD_LENGTH / 2));
|
MAX_PAYLOAD_LENGTH / 2));
|
||||||
// Reach EOF
|
// Reach EOF
|
||||||
assertEquals(-1, r.read(buf, 0, buf.length));
|
assertEquals(-1, r.read(buf, 0, buf.length));
|
||||||
context.assertIsSatisfied();
|
|
||||||
r.close();
|
r.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
package org.briarproject.bramble.transport;
|
package org.briarproject.bramble.transport;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class StreamWriterImplTest extends BrambleTestCase {
|
public class StreamWriterImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCloseWithoutWritingWritesFinalFrame() throws Exception {
|
public void testCloseWithoutWritingWritesFinalFrame() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Write an empty final frame
|
// Write an empty final frame
|
||||||
@@ -24,13 +22,11 @@ public class StreamWriterImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
||||||
w.close();
|
w.close();
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFlushWithoutBufferedDataWritesFrameAndFlushes()
|
public void testFlushWithoutBufferedDataWritesFrameAndFlushes()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
||||||
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -51,13 +47,11 @@ public class StreamWriterImplTest extends BrambleTestCase {
|
|||||||
oneOf(encrypter).flush();
|
oneOf(encrypter).flush();
|
||||||
}});
|
}});
|
||||||
w.close();
|
w.close();
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFlushWithBufferedDataWritesFrameAndFlushes()
|
public void testFlushWithBufferedDataWritesFrameAndFlushes()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
||||||
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -79,12 +73,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
|
|||||||
oneOf(encrypter).flush();
|
oneOf(encrypter).flush();
|
||||||
}});
|
}});
|
||||||
w.close();
|
w.close();
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSingleByteWritesWriteFullFrame() throws Exception {
|
public void testSingleByteWritesWriteFullFrame() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
||||||
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -103,12 +95,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
|
|||||||
oneOf(encrypter).flush();
|
oneOf(encrypter).flush();
|
||||||
}});
|
}});
|
||||||
w.close();
|
w.close();
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultiByteWritesWriteFullFrames() throws Exception {
|
public void testMultiByteWritesWriteFullFrames() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
||||||
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -134,12 +124,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
|
|||||||
oneOf(encrypter).flush();
|
oneOf(encrypter).flush();
|
||||||
}});
|
}});
|
||||||
w.close();
|
w.close();
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLargeMultiByteWriteWritesFullFrames() throws Exception {
|
public void testLargeMultiByteWriteWritesFullFrames() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
|
||||||
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
StreamWriterImpl w = new StreamWriterImpl(encrypter);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -157,6 +145,5 @@ public class StreamWriterImplTest extends BrambleTestCase {
|
|||||||
w.write(b);
|
w.write(b);
|
||||||
// There should be one byte left in the buffer
|
// There should be one byte left in the buffer
|
||||||
w.close();
|
w.close();
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -393,6 +393,76 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertNull(transportKeyManager.getStreamContext(txn, tag));
|
assertNull(transportKeyManager.getStreamContext(txn, tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetStreamContextOnlyAndMarkTag() throws Exception {
|
||||||
|
boolean alice = random.nextBoolean();
|
||||||
|
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
// Keep a copy of the tags
|
||||||
|
List<byte[]> tags = new ArrayList<>();
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
|
||||||
|
1000, alice, true);
|
||||||
|
will(returnValue(transportKeys));
|
||||||
|
// Get the current time (the start of time period 1000)
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(timePeriodLength * 1000));
|
||||||
|
// Encode the tags (3 sets)
|
||||||
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
|
exactly(3).of(transportCrypto).encodeTag(
|
||||||
|
with(any(byte[].class)), with(tagKey),
|
||||||
|
with(PROTOCOL_VERSION), with(i));
|
||||||
|
will(new EncodeTagAction(tags));
|
||||||
|
}
|
||||||
|
// Updated the transport keys (the keys are unaffected)
|
||||||
|
oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
|
||||||
|
will(returnValue(transportKeys));
|
||||||
|
// Save the keys
|
||||||
|
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||||
|
will(returnValue(keySetId));
|
||||||
|
// Encode a new tag after sliding the window
|
||||||
|
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
||||||
|
with(tagKey), with(PROTOCOL_VERSION),
|
||||||
|
with((long) REORDERING_WINDOW_SIZE));
|
||||||
|
will(new EncodeTagAction(tags));
|
||||||
|
// Save the reordering window (previous time period, base 1)
|
||||||
|
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
|
||||||
|
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||||
|
}});
|
||||||
|
|
||||||
|
// The timestamp is at the start of time period 1000
|
||||||
|
long timestamp = timePeriodLength * 1000;
|
||||||
|
assertEquals(keySetId, transportKeyManager.addRotationKeys(
|
||||||
|
txn, contactId, rootKey, timestamp, alice, true));
|
||||||
|
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||||
|
// Use the first tag (previous time period, stream number 0)
|
||||||
|
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
|
||||||
|
byte[] tag = tags.get(0);
|
||||||
|
// Repeated request should return same stream context
|
||||||
|
StreamContext ctx = transportKeyManager.getStreamContextOnly(txn, tag);
|
||||||
|
assertNotNull(ctx);
|
||||||
|
assertEquals(contactId, ctx.getContactId());
|
||||||
|
assertEquals(transportId, ctx.getTransportId());
|
||||||
|
assertEquals(tagKey, ctx.getTagKey());
|
||||||
|
assertEquals(headerKey, ctx.getHeaderKey());
|
||||||
|
assertEquals(0L, ctx.getStreamNumber());
|
||||||
|
ctx = transportKeyManager.getStreamContextOnly(txn, tag);
|
||||||
|
assertNotNull(ctx);
|
||||||
|
assertEquals(contactId, ctx.getContactId());
|
||||||
|
assertEquals(transportId, ctx.getTransportId());
|
||||||
|
assertEquals(tagKey, ctx.getTagKey());
|
||||||
|
assertEquals(headerKey, ctx.getHeaderKey());
|
||||||
|
assertEquals(0L, ctx.getStreamNumber());
|
||||||
|
// Then mark tag as recognised
|
||||||
|
transportKeyManager.markTagAsRecognised(txn, tag);
|
||||||
|
// Another tag should have been encoded
|
||||||
|
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
|
||||||
|
// Finally ensure the used tag is not recognised again
|
||||||
|
assertNull(transportKeyManager.getStreamContextOnly(txn, tag));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreUpdatedToCurrentPeriod() throws Exception {
|
public void testKeysAreUpdatedToCurrentPeriod() throws Exception {
|
||||||
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
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.fasterxml.jackson.core:jackson-core:2.13.0:jackson-core-2.13.0.jar:348bc59b348df2e807b356f1d62d2afb41a974073328abc773eb0932b855d2c8',
|
||||||
|
'com.fasterxml.jackson.core:jackson-databind:2.13.0:jackson-databind-2.13.0.jar:9c826d27176268777adcf97e1c6e2051c7e33a7aaa2c370c2e8c6077fd9da3f4',
|
||||||
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
|
'com.google.code.findbugs: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-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
|
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
|
||||||
@@ -15,6 +18,11 @@ dependencyVerification {
|
|||||||
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
|
'com.google.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.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||||
|
'com.squareup.okhttp3:mockwebserver:4.9.3:mockwebserver-4.9.3.jar:9c8c581c29f22f877a35d11380462f75bb24bf1886204fe835ee695594a2784e',
|
||||||
|
'com.squareup.okhttp3:okhttp:3.12.13:okhttp-3.12.13.jar:508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82',
|
||||||
|
'com.squareup.okhttp3:okhttp:4.9.3:okhttp-4.9.3.jar:93ecd6cba19d87dccfe566ec848d91aae799e3cf16c00709358ea69bd9227219',
|
||||||
|
'com.squareup.okio:okio:1.15.0:okio-1.15.0.jar:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2',
|
||||||
|
'com.squareup.okio:okio:2.8.0:okio-jvm-2.8.0.jar:4496b06e73982fcdd8a5393f46e5df2ce2fa4465df5895454cac68a32f09bbc8',
|
||||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
'javax.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',
|
||||||
@@ -26,7 +34,7 @@ dependencyVerification {
|
|||||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||||
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
|
'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38',
|
||||||
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
|
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
|
||||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
'org.checkerframework:checker-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',
|
||||||
@@ -38,7 +46,11 @@ dependencyVerification {
|
|||||||
'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.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
|
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
|
||||||
|
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10:kotlin-stdlib-common-1.4.10.jar:4681f2d436a68c7523595d84ed5758e1382f9da0f67c91e6a848690d711274fe',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
|
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
|
||||||
|
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10:kotlin-stdlib-jdk7-1.4.10.jar:f9566380c08722c780ce33ceee23e98ddf765ca98fabd3e2fabae7975c8d232b',
|
||||||
|
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10:kotlin-stdlib-jdk8-1.4.10.jar:39b7a9442d7a3865e0f4a732c56c1d5da0e11ffb3bb82a461d32deb0c0ca7673',
|
||||||
|
'org.jetbrains.kotlin:kotlin-stdlib:1.4.10:kotlin-stdlib-1.4.10.jar:01ecb09782c042b931c1839acf21a188340b295d05400afd6e3415d4475b8daa',
|
||||||
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
|
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
|
||||||
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
|
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
|
||||||
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ dependencies {
|
|||||||
def jna_version = '4.5.2'
|
def jna_version = '4.5.2'
|
||||||
implementation "net.java.dev.jna:jna:$jna_version"
|
implementation "net.java.dev.jna:jna:$jna_version"
|
||||||
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
||||||
tor 'org.briarproject:tor:0.3.5.15'
|
tor "org.briarproject:tor:$tor_version"
|
||||||
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
|
tor "org.briarproject:obfs4proxy:$obfs4proxy_version@zip"
|
||||||
|
|
||||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ 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"
|
|
||||||
|
|
||||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
package org.briarproject.bramble.plugin.tor;
|
||||||
|
|
||||||
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;
|
||||||
@@ -58,6 +59,7 @@ public class UnixTorPluginFactory 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
|
||||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@@ -73,7 +75,8 @@ public class UnixTorPluginFactory 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.networkManager = networkManager;
|
this.networkManager = networkManager;
|
||||||
@@ -88,6 +91,7 @@ public class UnixTorPluginFactory 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
|
||||||
@@ -128,7 +132,8 @@ public class UnixTorPluginFactory 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);
|
||||||
UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
|
UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
|
||||||
networkManager, locationUtils, torSocketFactory, clock,
|
networkManager, locationUtils, torSocketFactory, clock,
|
||||||
resourceProvider, circumventionProvider, batteryManager,
|
resourceProvider, circumventionProvider, batteryManager,
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ package org.briarproject.bramble.plugin.tor;
|
|||||||
|
|
||||||
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
|
||||||
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;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.ResourceProvider;
|
import org.briarproject.bramble.api.system.ResourceProvider;
|
||||||
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
import org.briarproject.bramble.api.system.WakefulIoExecutor;
|
||||||
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
|
||||||
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
|
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
|
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
|
||||||
@@ -33,11 +36,14 @@ import javax.inject.Inject;
|
|||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
|
||||||
|
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
|
||||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||||
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
|
||||||
@@ -56,14 +62,21 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
.injectEagerSingletons(component);
|
.injectEagerSingletons(component);
|
||||||
// Share a failure counter among all the test instances
|
// Share a failure counter among all the test instances
|
||||||
AtomicInteger failures = new AtomicInteger(0);
|
AtomicInteger failures = new AtomicInteger(0);
|
||||||
List<String> bridges =
|
CircumventionProvider provider = component.getCircumventionProvider();
|
||||||
component.getCircumventionProvider().getBridges(false);
|
List<Params> states = new ArrayList<>();
|
||||||
List<Params> states = new ArrayList<>(bridges.size());
|
for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
|
||||||
for (String bridge : bridges) states.add(new Params(bridge, failures));
|
states.add(new Params(bridge, DEFAULT_OBFS4, failures, false));
|
||||||
|
}
|
||||||
|
for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
|
||||||
|
states.add(new Params(bridge, NON_DEFAULT_OBFS4, failures, false));
|
||||||
|
}
|
||||||
|
for (String bridge : provider.getBridges(MEEK)) {
|
||||||
|
states.add(new Params(bridge, MEEK, failures, true));
|
||||||
|
}
|
||||||
return states;
|
return states;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static long TIMEOUT = SECONDS.toMillis(60);
|
private final static long TIMEOUT = MINUTES.toMillis(5);
|
||||||
private final static int NUM_FAILURES_ALLOWED = 1;
|
private final static int NUM_FAILURES_ALLOWED = 1;
|
||||||
|
|
||||||
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
||||||
@@ -88,16 +101,16 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
BackoffFactory backoffFactory;
|
BackoffFactory backoffFactory;
|
||||||
@Inject
|
@Inject
|
||||||
Clock clock;
|
Clock clock;
|
||||||
|
@Inject
|
||||||
|
CryptoComponent crypto;
|
||||||
|
|
||||||
private final File torDir = getTestDirectory();
|
private final File torDir = getTestDirectory();
|
||||||
private final String bridge;
|
private final Params params;
|
||||||
private final AtomicInteger failures;
|
|
||||||
|
|
||||||
private UnixTorPluginFactory factory;
|
private UnixTorPluginFactory factory;
|
||||||
|
|
||||||
public BridgeTest(Params params) {
|
public BridgeTest(Params params) {
|
||||||
bridge = params.bridge;
|
this.params = params;
|
||||||
failures = params.failures;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -117,6 +130,7 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
LocationUtils locationUtils = () -> "US";
|
LocationUtils locationUtils = () -> "US";
|
||||||
SocketFactory torSocketFactory = SocketFactory.getDefault();
|
SocketFactory torSocketFactory = SocketFactory.getDefault();
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
CircumventionProvider bridgeProvider = new CircumventionProvider() {
|
CircumventionProvider bridgeProvider = new CircumventionProvider() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isTorProbablyBlocked(String countryCode) {
|
public boolean isTorProbablyBlocked(String countryCode) {
|
||||||
@@ -129,20 +143,20 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean needsMeek(String countryCode) {
|
public BridgeType getBestBridgeType(String countryCode) {
|
||||||
return false;
|
return params.bridgeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getBridges(boolean useMeek) {
|
public List<String> getBridges(BridgeType bridgeType) {
|
||||||
return singletonList(bridge);
|
return singletonList(params.bridge);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
|
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
|
||||||
networkManager, locationUtils, eventBus, torSocketFactory,
|
networkManager, locationUtils, eventBus, torSocketFactory,
|
||||||
backoffFactory, resourceProvider, bridgeProvider,
|
backoffFactory, resourceProvider, bridgeProvider,
|
||||||
batteryManager, clock, torDir, DEFAULT_SOCKS_PORT,
|
batteryManager, clock, torDir, DEFAULT_SOCKS_PORT,
|
||||||
DEFAULT_CONTROL_PORT);
|
DEFAULT_CONTROL_PORT, crypto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -157,7 +171,7 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
assertNotNull(duplexPlugin);
|
assertNotNull(duplexPlugin);
|
||||||
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
|
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
|
||||||
|
|
||||||
LOG.warning("Testing " + bridge);
|
LOG.warning("Testing " + params.bridge);
|
||||||
try {
|
try {
|
||||||
plugin.start();
|
plugin.start();
|
||||||
long start = clock.currentTimeMillis();
|
long start = clock.currentTimeMillis();
|
||||||
@@ -167,8 +181,11 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
if (plugin.getState() != ACTIVE) {
|
if (plugin.getState() != ACTIVE) {
|
||||||
LOG.warning("Could not connect to Tor within timeout");
|
LOG.warning("Could not connect to Tor within timeout");
|
||||||
if (failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
|
if (params.failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
|
||||||
fail(failures.get() + " bridges are unreachable");
|
fail(params.failures.get() + " bridges are unreachable");
|
||||||
|
}
|
||||||
|
if (params.mustSucceed) {
|
||||||
|
fail("essential bridge is unreachable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -179,11 +196,16 @@ public class BridgeTest extends BrambleTestCase {
|
|||||||
private static class Params {
|
private static class Params {
|
||||||
|
|
||||||
private final String bridge;
|
private final String bridge;
|
||||||
|
private final BridgeType bridgeType;
|
||||||
private final AtomicInteger failures;
|
private final AtomicInteger failures;
|
||||||
|
private final boolean mustSucceed;
|
||||||
|
|
||||||
private Params(String bridge, AtomicInteger failures) {
|
private Params(String bridge, BridgeType bridgeType,
|
||||||
|
AtomicInteger failures, boolean mustSucceed) {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
|
this.bridgeType = bridgeType;
|
||||||
this.failures = failures;
|
this.failures = failures;
|
||||||
|
this.mustSucceed = mustSucceed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ dependencyVerification {
|
|||||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||||
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
|
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
|
||||||
'org.briarproject:tor:0.3.5.15:tor-0.3.5.15.jar:2ff5b5a3b5eaa97d699629ad24ba9584b3199d0ffdb1ea7d8a02de3016b80e7a',
|
'org.briarproject:tor:0.3.5.17:tor-0.3.5.17.jar:ce0e1f4d8f14878e61b23a35a452bc0f2a8e3117ced5a74773cd78475fa7af39',
|
||||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 10401
|
versionCode 10404
|
||||||
versionName "1.4.1"
|
versionName "1.4.4"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.android"
|
||||||
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -131,17 +131,17 @@ dependencies {
|
|||||||
def espressoVersion = '3.3.0'
|
def espressoVersion = '3.3.0'
|
||||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
||||||
testImplementation 'androidx.test:runner:1.3.0'
|
testImplementation 'androidx.test:runner:1.4.0'
|
||||||
testImplementation 'androidx.test.ext:junit:1.1.2'
|
testImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
testImplementation 'androidx.fragment:fragment-testing:1.3.4'
|
testImplementation 'androidx.fragment:fragment-testing:1.4.0'
|
||||||
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||||
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
||||||
testImplementation 'org.robolectric:robolectric:4.3.1'
|
testImplementation 'org.robolectric:robolectric:4.4'
|
||||||
testImplementation 'org.mockito:mockito-core:3.9.0'
|
testImplementation 'org.mockito:mockito-core:3.9.0'
|
||||||
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"
|
||||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
|
|
||||||
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
-keepattributes SourceFile, LineNumberTable, *Annotation*, Signature, InnerClasses, EnclosingMethod
|
-keepattributes SourceFile, LineNumberTable, *Annotation*, Signature, InnerClasses, EnclosingMethod
|
||||||
|
|
||||||
|
-keep,includedescriptorclasses class org.briarproject.briar.android.**,org.briarproject.briar.api.android.** { *; }
|
||||||
|
|
||||||
# QR codes
|
# QR codes
|
||||||
-keep class com.google.zxing.Result
|
-keep class com.google.zxing.Result
|
||||||
-keepclassmembers enum * {
|
-keepclassmembers enum * {
|
||||||
|
|||||||
@@ -99,6 +99,10 @@
|
|||||||
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
|
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
|
||||||
android:label="@string/app_name" />
|
android:label="@string/app_name" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="org.briarproject.briar.android.splash.ExpiredOldAndroidActivity"
|
||||||
|
android:label="@string/app_name" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.login.StartupActivity"
|
android:name="org.briarproject.briar.android.login.StartupActivity"
|
||||||
android:label="@string/app_name" />
|
android:label="@string/app_name" />
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import org.briarproject.briar.android.hotspot.HotspotModule;
|
|||||||
import org.briarproject.briar.android.introduction.IntroductionModule;
|
import org.briarproject.briar.android.introduction.IntroductionModule;
|
||||||
import org.briarproject.briar.android.logging.LoggingModule;
|
import org.briarproject.briar.android.logging.LoggingModule;
|
||||||
import org.briarproject.briar.android.login.LoginModule;
|
import org.briarproject.briar.android.login.LoginModule;
|
||||||
|
import org.briarproject.briar.android.mailbox.MailboxModule;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
|
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
|
||||||
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
|
||||||
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
import org.briarproject.briar.android.privategroup.list.GroupListModule;
|
||||||
@@ -55,6 +56,7 @@ import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
|||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.android.DozeWatchdog;
|
import org.briarproject.briar.api.android.DozeWatchdog;
|
||||||
import org.briarproject.briar.api.android.LockManager;
|
import org.briarproject.briar.api.android.LockManager;
|
||||||
|
import org.briarproject.briar.api.android.NetworkUsageMetrics;
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
import org.briarproject.briar.api.test.TestAvatarCreator;
|
import org.briarproject.briar.api.test.TestAvatarCreator;
|
||||||
|
|
||||||
@@ -103,6 +105,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
SharingModule.class,
|
SharingModule.class,
|
||||||
HotspotModule.class,
|
HotspotModule.class,
|
||||||
TransferDataModule.class,
|
TransferDataModule.class,
|
||||||
|
MailboxModule.class,
|
||||||
})
|
})
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
|
|
||||||
@@ -112,7 +115,7 @@ public class AppModule {
|
|||||||
@Inject
|
@Inject
|
||||||
ScreenFilterMonitor screenFilterMonitor;
|
ScreenFilterMonitor screenFilterMonitor;
|
||||||
@Inject
|
@Inject
|
||||||
NetworkUsageLogger networkUsageLogger;
|
NetworkUsageMetrics networkUsageMetrics;
|
||||||
@Inject
|
@Inject
|
||||||
DozeWatchdog dozeWatchdog;
|
DozeWatchdog dozeWatchdog;
|
||||||
@Inject
|
@Inject
|
||||||
@@ -285,11 +288,12 @@ public class AppModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
NetworkUsageLogger provideNetworkUsageLogger(
|
@Singleton
|
||||||
|
NetworkUsageMetrics provideNetworkUsageMetrics(
|
||||||
LifecycleManager lifecycleManager) {
|
LifecycleManager lifecycleManager) {
|
||||||
NetworkUsageLogger networkUsageLogger = new NetworkUsageLogger();
|
NetworkUsageMetrics networkUsageMetrics = new NetworkUsageMetricsImpl();
|
||||||
lifecycleManager.registerService(networkUsageLogger);
|
lifecycleManager.registerService(networkUsageMetrics);
|
||||||
return networkUsageLogger;
|
return networkUsageMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -335,6 +339,21 @@ public class AppModule {
|
|||||||
public boolean shouldEnableDisappearingMessages() {
|
public boolean shouldEnableDisappearingMessages() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnablePrivateGroupsInCore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableForumsInCore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableBlogsInCore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package org.briarproject.briar.android;
|
|
||||||
|
|
||||||
import android.net.TrafficStats;
|
|
||||||
import android.os.Process;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
|
||||||
|
|
||||||
class NetworkUsageLogger implements Service {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(NetworkUsageLogger.class.getName());
|
|
||||||
|
|
||||||
private volatile long startTime, rxBytes, txBytes;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startService() {
|
|
||||||
startTime = now();
|
|
||||||
int uid = Process.myUid();
|
|
||||||
rxBytes = TrafficStats.getUidRxBytes(uid);
|
|
||||||
txBytes = TrafficStats.getUidTxBytes(uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopService() {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
long sessionDuration = now() - startTime;
|
|
||||||
int uid = Process.myUid();
|
|
||||||
long rx = TrafficStats.getUidRxBytes(uid) - rxBytes;
|
|
||||||
long tx = TrafficStats.getUidTxBytes(uid) - txBytes;
|
|
||||||
LOG.info("Duration " + (sessionDuration / 1000) + " seconds");
|
|
||||||
LOG.info("Received " + rx + " bytes");
|
|
||||||
LOG.info("Sent " + tx + " bytes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.net.TrafficStats;
|
||||||
|
import android.os.Process;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.api.android.NetworkUsageMetrics;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class NetworkUsageMetricsImpl implements NetworkUsageMetrics {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(NetworkUsageMetricsImpl.class.getName());
|
||||||
|
|
||||||
|
private volatile long startTime, rxBytes, txBytes;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startService() {
|
||||||
|
startTime = now();
|
||||||
|
int uid = Process.myUid();
|
||||||
|
rxBytes = TrafficStats.getUidRxBytes(uid);
|
||||||
|
txBytes = TrafficStats.getUidTxBytes(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopService() {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
Metrics metrics = getMetrics();
|
||||||
|
LOG.info("Duration " + (metrics.getSessionDurationMs() / 1000)
|
||||||
|
+ " seconds");
|
||||||
|
LOG.info("Received " + metrics.getRxBytes() + " bytes");
|
||||||
|
LOG.info("Sent " + metrics.getTxBytes() + " bytes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Metrics getMetrics() {
|
||||||
|
long sessionDurationMs = now() - startTime;
|
||||||
|
int uid = Process.myUid();
|
||||||
|
long rx = TrafficStats.getUidRxBytes(uid) - rxBytes;
|
||||||
|
long tx = TrafficStats.getUidTxBytes(uid) - txBytes;
|
||||||
|
return new Metrics(sessionDurationMs, rx, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android;
|
|||||||
|
|
||||||
import org.briarproject.briar.BuildConfig;
|
import org.briarproject.briar.BuildConfig;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.concurrent.TimeUnit.DAYS;
|
import static java.util.concurrent.TimeUnit.DAYS;
|
||||||
|
|
||||||
public interface TestingConstants {
|
public interface TestingConstants {
|
||||||
@@ -19,10 +20,15 @@ public interface TestingConstants {
|
|||||||
*/
|
*/
|
||||||
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
|
boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD;
|
||||||
|
|
||||||
|
boolean IS_OLD_ANDROID = SDK_INT <= 19;
|
||||||
|
long OLD_ANDROID_WARN_DATE = 1659225600_000L; // 2022-07-31
|
||||||
|
long OLD_ANDROID_EXPIRY_DATE = 1675123200_000L; // 2023-01-31
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug builds expire after 90 days. Release builds expire after 292
|
* Debug builds expire after 90 days. Release builds running on Android 4
|
||||||
* million years.
|
* expire at a set date, otherwise they expire after 292 million years.
|
||||||
*/
|
*/
|
||||||
long EXPIRY_DATE = IS_DEBUG_BUILD ?
|
long EXPIRY_DATE = IS_DEBUG_BUILD ?
|
||||||
BuildConfig.BuildTimestamp + DAYS.toMillis(90) : Long.MAX_VALUE;
|
BuildConfig.BuildTimestamp + DAYS.toMillis(90)
|
||||||
|
: (IS_OLD_ANDROID ? OLD_ANDROID_EXPIRY_DATE : Long.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ import org.briarproject.briar.android.sharing.ShareBlogFragment;
|
|||||||
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
import org.briarproject.briar.android.sharing.ShareForumActivity;
|
||||||
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
import org.briarproject.briar.android.sharing.ShareForumFragment;
|
||||||
import org.briarproject.briar.android.sharing.SharingModule;
|
import org.briarproject.briar.android.sharing.SharingModule;
|
||||||
|
import org.briarproject.briar.android.splash.ExpiredOldAndroidActivity;
|
||||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||||
import org.briarproject.briar.android.test.TestDataActivity;
|
import org.briarproject.briar.android.test.TestDataActivity;
|
||||||
|
|
||||||
@@ -182,6 +183,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(RemovableDriveActivity activity);
|
void inject(RemovableDriveActivity activity);
|
||||||
|
|
||||||
|
void inject(ExpiredOldAndroidActivity activity);
|
||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
|
|
||||||
void inject(SetupFragment fragment);
|
void inject(SetupFragment fragment);
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ public class ContactListItem extends ContactItem
|
|||||||
item.unread, item.timestamp);
|
item.unread, item.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactListItem(ContactListItem item, ConversationMessageHeader h) {
|
ContactListItem(ContactListItem item, long timestamp, boolean read) {
|
||||||
this(item.getContact(), item.getAuthorInfo(), item.isConnected(), false,
|
this(item.getContact(), item.getAuthorInfo(), item.isConnected(), false,
|
||||||
h.isRead() ? item.unread : item.unread + 1,
|
read ? item.unread : item.unread + 1,
|
||||||
Math.max(h.getTimestamp(), item.timestamp));
|
Math.max(timestamp, item.timestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.briarproject.briar.api.client.MessageTracker;
|
|||||||
import org.briarproject.briar.api.conversation.ConversationManager;
|
import org.briarproject.briar.api.conversation.ConversationManager;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||||
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
|
||||||
|
import org.briarproject.briar.api.conversation.event.ConversationMessageTrackedEvent;
|
||||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||||
import org.briarproject.briar.api.identity.AuthorManager;
|
import org.briarproject.briar.api.identity.AuthorManager;
|
||||||
|
|
||||||
@@ -131,13 +132,14 @@ public class ContactsViewModel extends DbViewModel implements EventListener {
|
|||||||
} else if (e instanceof ContactRemovedEvent) {
|
} else if (e instanceof ContactRemovedEvent) {
|
||||||
LOG.info("Contact removed, removing item");
|
LOG.info("Contact removed, removing item");
|
||||||
removeItem(((ContactRemovedEvent) e).getContactId());
|
removeItem(((ContactRemovedEvent) e).getContactId());
|
||||||
} else if (e instanceof ConversationMessageReceivedEvent) {
|
} else if (e instanceof ConversationMessageTrackedEvent) {
|
||||||
LOG.info("Conversation message received, updating item");
|
LOG.info("Conversation message tracked, updating item");
|
||||||
ConversationMessageReceivedEvent<?> p =
|
ConversationMessageTrackedEvent p =
|
||||||
(ConversationMessageReceivedEvent<?>) e;
|
(ConversationMessageTrackedEvent) e;
|
||||||
ConversationMessageHeader h = p.getMessageHeader();
|
long timestamp = p.getTimestamp();
|
||||||
updateItem(p.getContactId(), item -> new ContactListItem(item, h),
|
boolean read = p.getRead();
|
||||||
true);
|
updateItem(p.getContactId(),
|
||||||
|
item -> new ContactListItem(item, timestamp, read), true);
|
||||||
} else if (e instanceof AvatarUpdatedEvent) {
|
} else if (e instanceof AvatarUpdatedEvent) {
|
||||||
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
|
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
|
||||||
updateItem(a.getContactId(), item -> new ContactListItem(item,
|
updateItem(a.getContactId(), item -> new ContactListItem(item,
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgre
|
|||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.QrCodeScanned;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.QrCodeScanned;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
|
import org.briarproject.briar.android.qrcode.CameraException;
|
||||||
|
import org.briarproject.briar.android.qrcode.CameraView;
|
||||||
import org.briarproject.briar.android.view.QrCodeView;
|
import org.briarproject.briar.android.view.QrCodeView;
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Contact
|
|||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted;
|
||||||
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
|
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
|
||||||
import org.briarproject.briar.android.util.QrCodeUtils;
|
import org.briarproject.briar.android.qrcode.QrCodeDecoder;
|
||||||
|
import org.briarproject.briar.android.qrcode.QrCodeUtils;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import org.briarproject.bramble.api.settings.SettingsManager;
|
|||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.NetworkConfig;
|
import org.briarproject.briar.android.hotspot.HotspotState.NetworkConfig;
|
||||||
import org.briarproject.briar.android.util.QrCodeUtils;
|
import org.briarproject.briar.android.qrcode.QrCodeUtils;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -52,7 +52,7 @@ import static java.util.Objects.requireNonNull;
|
|||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.briar.android.util.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
|
import static org.briarproject.briar.android.qrcode.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.handleException;
|
import static org.briarproject.briar.android.util.UiUtils.handleException;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -312,12 +312,19 @@ class HotspotManager {
|
|||||||
}
|
}
|
||||||
GroupInfoListener groupListener = group -> {
|
GroupInfoListener groupListener = group -> {
|
||||||
boolean valid = isGroupValid(group);
|
boolean valid = isGroupValid(group);
|
||||||
// If the group is valid, set the hotspot to started. If we don't
|
if (valid) {
|
||||||
// have any attempts left, we try what we got
|
// the group is valid, set the hotspot to started.
|
||||||
if (valid || attempt >= MAX_GROUP_INFO_ATTEMPTS) {
|
onHotspotStarted(group);
|
||||||
|
} else if (attempt < MAX_GROUP_INFO_ATTEMPTS) {
|
||||||
|
// we have attempts left, try again
|
||||||
|
retryRequestingGroupInfo(attempt);
|
||||||
|
} else if (group != null) {
|
||||||
|
// no attempts left, but group is not null, try what we got
|
||||||
onHotspotStarted(group);
|
onHotspotStarted(group);
|
||||||
} else {
|
} else {
|
||||||
retryRequestingGroupInfo(attempt);
|
// no attempts left and group is null, fail
|
||||||
|
releaseHotspotWithError(ctx.getString(
|
||||||
|
R.string.hotspot_error_start_callback_no_group_info));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
@@ -366,13 +373,8 @@ class HotspotManager {
|
|||||||
private void retryRequestingGroupInfo(int attempt) {
|
private void retryRequestingGroupInfo(int attempt) {
|
||||||
LOG.info("retrying to request group info");
|
LOG.info("retrying to request group info");
|
||||||
// On some devices we need to wait for the group info to become available
|
// On some devices we need to wait for the group info to become available
|
||||||
if (attempt < MAX_GROUP_INFO_ATTEMPTS) {
|
handler.postDelayed(() -> requestGroupInfo(attempt + 1),
|
||||||
handler.postDelayed(() -> requestGroupInfo(attempt + 1),
|
RETRY_DELAY_MILLIS);
|
||||||
RETRY_DELAY_MILLIS);
|
|
||||||
} else {
|
|
||||||
releaseHotspotWithError(ctx.getString(
|
|
||||||
R.string.hotspot_error_start_callback_no_group_info));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.android.hotspot.HotspotState.WebsiteConfig;
|
import org.briarproject.briar.android.hotspot.HotspotState.WebsiteConfig;
|
||||||
import org.briarproject.briar.android.util.QrCodeUtils;
|
import org.briarproject.briar.android.qrcode.QrCodeUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -27,7 +27,7 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||||
import static org.briarproject.briar.android.hotspot.WebServer.PORT;
|
import static org.briarproject.briar.android.hotspot.WebServer.PORT;
|
||||||
import static org.briarproject.briar.android.util.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
|
import static org.briarproject.briar.android.qrcode.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.briar.android.mailbox;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public interface MailboxModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(MailboxPairViewModel.class)
|
||||||
|
ViewModel bindMailboxViewModel(
|
||||||
|
MailboxPairViewModel mailboxPairViewModel);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package org.briarproject.briar.android.mailbox;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import com.google.zxing.Result;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
import org.briarproject.briar.android.qrcode.QrCodeDecoder;
|
||||||
|
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.UiThread;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
|
class MailboxPairViewModel extends DbViewModel
|
||||||
|
implements QrCodeDecoder.ResultCallback {
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(MailboxPairViewModel.class.getName());
|
||||||
|
|
||||||
|
private static final int VERSION_REQUIRED = 32;
|
||||||
|
|
||||||
|
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
|
||||||
|
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final QrCodeDecoder qrCodeDecoder;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String onionAddress = null;
|
||||||
|
@Nullable
|
||||||
|
private String setupToken = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MailboxPairViewModel(
|
||||||
|
Application app,
|
||||||
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
TransactionManager db,
|
||||||
|
AndroidExecutor androidExecutor,
|
||||||
|
@IoExecutor Executor ioExecutor,
|
||||||
|
CryptoComponent crypto) {
|
||||||
|
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||||
|
this.crypto = crypto;
|
||||||
|
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onQrCodeDecoded(Result result) {
|
||||||
|
LOG.info("Got result from decoder");
|
||||||
|
byte[] bytes = result.getText().getBytes(ISO_8859_1);
|
||||||
|
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("QR code length in bytes: " + bytes.length);
|
||||||
|
if (bytes.length != 65) {
|
||||||
|
LOG.info("QR code has wrong length");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("QR code version: " + bytes[0]);
|
||||||
|
if (bytes[0] != VERSION_REQUIRED) {
|
||||||
|
LOG.info("QR code has wrong version");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
|
||||||
|
onionAddress = crypto.encodeOnionAddress(onionPubKey);
|
||||||
|
setupToken = StringUtils.toHexString(Arrays.copyOfRange(bytes, 33, 65))
|
||||||
|
.toLowerCase();
|
||||||
|
LOG.info("QR code is valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
QrCodeDecoder getQrCodeDecoder() {
|
||||||
|
return qrCodeDecoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -75,12 +75,15 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
|
|||||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
||||||
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
||||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
||||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
|
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.formatDateFull;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
|
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.shouldWarnOldAndroidExpiry;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -137,9 +140,11 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
setContentView(R.layout.activity_nav_drawer);
|
setContentView(R.layout.activity_nav_drawer);
|
||||||
|
|
||||||
BriarApplication app = (BriarApplication) getApplication();
|
BriarApplication app = (BriarApplication) getApplication();
|
||||||
if (IS_DEBUG_BUILD && !app.isInstrumentationTest()) {
|
if (!app.isInstrumentationTest()) {
|
||||||
navDrawerViewModel.showExpiryWarning()
|
if (IS_DEBUG_BUILD || shouldWarnOldAndroidExpiry()) {
|
||||||
.observe(this, this::showExpiryWarning);
|
navDrawerViewModel.showExpiryWarning()
|
||||||
|
.observe(this, this::showExpiryWarning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
navDrawerViewModel.shouldAskForDozeWhitelisting().observe(this, ask -> {
|
navDrawerViewModel.shouldAskForDozeWhitelisting().observe(this, ask -> {
|
||||||
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
|
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
|
||||||
@@ -207,7 +212,9 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
lockManager.checkIfLockable();
|
lockManager.checkIfLockable();
|
||||||
if (IS_DEBUG_BUILD) navDrawerViewModel.checkExpiryWarning();
|
if (IS_DEBUG_BUILD || shouldWarnOldAndroidExpiry()) {
|
||||||
|
navDrawerViewModel.checkExpiryWarning();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -377,14 +384,23 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String text;
|
||||||
|
if (IS_DEBUG_BUILD) {
|
||||||
|
text = getResources().getQuantityString(
|
||||||
|
R.plurals.expiry_warning, (int) daysUntilExpiry,
|
||||||
|
(int) daysUntilExpiry);
|
||||||
|
} else {
|
||||||
|
text = getResources().getQuantityString(
|
||||||
|
R.plurals.old_android_expiry_warning, (int) daysUntilExpiry,
|
||||||
|
formatDateFull(this, EXPIRY_DATE),
|
||||||
|
(int) daysUntilExpiry);
|
||||||
|
}
|
||||||
|
|
||||||
ViewGroup expiryWarning = findViewById(R.id.expiryWarning);
|
ViewGroup expiryWarning = findViewById(R.id.expiryWarning);
|
||||||
if (show) {
|
if (show) {
|
||||||
// show expiry warning text
|
// show expiry warning text
|
||||||
TextView expiryWarningText =
|
TextView expiryWarningText =
|
||||||
expiryWarning.findViewById(R.id.expiryWarningText);
|
expiryWarning.findViewById(R.id.expiryWarningText);
|
||||||
String text = getResources().getQuantityString(
|
|
||||||
R.plurals.expiry_warning, (int) daysUntilExpiry,
|
|
||||||
(int) daysUntilExpiry);
|
|
||||||
expiryWarningText.setText(text);
|
expiryWarningText.setText(text);
|
||||||
// make close button functional
|
// make close button functional
|
||||||
ImageView expiryWarningClose =
|
ImageView expiryWarningClose =
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.briarproject.briar.android.contact.add.nearby;
|
package org.briarproject.briar.android.qrcode;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
class CameraException extends IOException {
|
public class CameraException extends IOException {
|
||||||
|
|
||||||
CameraException(String message) {
|
CameraException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.contact.add.nearby;
|
package org.briarproject.briar.android.qrcode;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.contact.add.nearby;
|
package org.briarproject.briar.android.qrcode;
|
||||||
|
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface PreviewConsumer {
|
public interface PreviewConsumer {
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
void start(Camera camera, int cameraIndex);
|
void start(Camera camera, int cameraIndex);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.contact.add.nearby;
|
package org.briarproject.briar.android.qrcode;
|
||||||
|
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
import android.hardware.Camera.CameraInfo;
|
import android.hardware.Camera.CameraInfo;
|
||||||
@@ -32,7 +32,7 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(QrCodeDecoder.class.getName());
|
private static final Logger LOG = getLogger(QrCodeDecoder.class.getName());
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
private int cameraIndex = 0;
|
private int cameraIndex = 0;
|
||||||
|
|
||||||
QrCodeDecoder(AndroidExecutor androidExecutor,
|
public QrCodeDecoder(AndroidExecutor androidExecutor,
|
||||||
@IoExecutor Executor ioExecutor, ResultCallback callback) {
|
@IoExecutor Executor ioExecutor, ResultCallback callback) {
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
@@ -127,7 +127,7 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface ResultCallback {
|
public interface ResultCallback {
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
void onQrCodeDecoded(Result result);
|
void onQrCodeDecoded(Result result);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.util;
|
package org.briarproject.briar.android.qrcode;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
@@ -23,11 +23,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class QrCodeUtils {
|
public class QrCodeUtils {
|
||||||
|
public static final double HOTSPOT_QRCODE_FACTOR = 0.35;
|
||||||
|
|
||||||
private static final Logger LOG = getLogger(QrCodeUtils.class.getName());
|
private static final Logger LOG = getLogger(QrCodeUtils.class.getName());
|
||||||
|
|
||||||
public static final double HOTSPOT_QRCODE_FACTOR = 0.35;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Bitmap createQrCode(DisplayMetrics dm, String input) {
|
public static Bitmap createQrCode(DisplayMetrics dm, String input) {
|
||||||
return createQrCode(Math.min(dm.widthPixels, dm.heightPixels), input);
|
return createQrCode(Math.min(dm.widthPixels, dm.heightPixels), input);
|
||||||
@@ -28,6 +28,8 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
||||||
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
|
||||||
|
import org.briarproject.briar.api.android.NetworkUsageMetrics;
|
||||||
|
import org.briarproject.briar.api.android.NetworkUsageMetrics.Metrics;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -65,9 +67,11 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
|||||||
class BriarReportCollector {
|
class BriarReportCollector {
|
||||||
|
|
||||||
private final Context ctx;
|
private final Context ctx;
|
||||||
|
private final NetworkUsageMetrics networkUsageMetrics;
|
||||||
|
|
||||||
BriarReportCollector(Context ctx) {
|
BriarReportCollector(Context ctx, NetworkUsageMetrics networkUsageMetrics) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
|
this.networkUsageMetrics = networkUsageMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
|
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
|
||||||
@@ -81,6 +85,7 @@ class BriarReportCollector {
|
|||||||
.add(getMemory())
|
.add(getMemory())
|
||||||
.add(getStorage())
|
.add(getStorage())
|
||||||
.add(getConnectivity())
|
.add(getConnectivity())
|
||||||
|
.add(getNetworkUsage())
|
||||||
.add(getBuildConfig())
|
.add(getBuildConfig())
|
||||||
.add(getLogcat(logs))
|
.add(getLogcat(logs))
|
||||||
.add(getDeviceFeatures());
|
.add(getDeviceFeatures());
|
||||||
@@ -298,6 +303,16 @@ class BriarReportCollector {
|
|||||||
connectivityInfo);
|
connectivityInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ReportItem getNetworkUsage() {
|
||||||
|
Metrics metrics = networkUsageMetrics.getMetrics();
|
||||||
|
MultiReportInfo networkUsage = new MultiReportInfo()
|
||||||
|
.add("SessionDuration", metrics.getSessionDurationMs())
|
||||||
|
.add("BytesReceived", metrics.getRxBytes())
|
||||||
|
.add("BytesSent", metrics.getTxBytes());
|
||||||
|
return new ReportItem("NetworkUsage", R.string.dev_report_network_usage,
|
||||||
|
networkUsage);
|
||||||
|
}
|
||||||
|
|
||||||
private ReportItem getBuildConfig() {
|
private ReportItem getBuildConfig() {
|
||||||
MultiReportInfo buildConfig = new MultiReportInfo()
|
MultiReportInfo buildConfig = new MultiReportInfo()
|
||||||
.add("GitHash", BuildConfig.GitHash)
|
.add("GitHash", BuildConfig.GitHash)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
|
|||||||
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
|
import org.briarproject.briar.api.android.NetworkUsageMetrics;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -69,12 +70,13 @@ class ReportViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ReportViewModel(@NonNull Application application,
|
ReportViewModel(@NonNull Application application,
|
||||||
|
NetworkUsageMetrics networkUsageMetrics,
|
||||||
CachingLogHandler logHandler,
|
CachingLogHandler logHandler,
|
||||||
LogDecrypter logDecrypter,
|
LogDecrypter logDecrypter,
|
||||||
DevReporter reporter,
|
DevReporter reporter,
|
||||||
PluginManager pluginManager) {
|
PluginManager pluginManager) {
|
||||||
super(application);
|
super(application);
|
||||||
collector = new BriarReportCollector(application);
|
collector = new BriarReportCollector(application, networkUsageMetrics);
|
||||||
this.logHandler = logHandler;
|
this.logHandler = logHandler;
|
||||||
this.logDecrypter = logDecrypter;
|
this.logDecrypter = logDecrypter;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package org.briarproject.briar.android.splash;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.android.controller.BriarController;
|
||||||
|
import org.briarproject.briar.android.logout.ExitActivity;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class ExpiredOldAndroidActivity extends BaseActivity {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
BriarController briarController;
|
||||||
|
@Inject
|
||||||
|
AndroidWakeLockManager wakeLockManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_expired_old_android);
|
||||||
|
findViewById(R.id.delete_account_button).setOnClickListener(v -> {
|
||||||
|
// Hold a wake lock to ensure we exit before the device goes to sleep
|
||||||
|
wakeLockManager.runWakefully(() -> {
|
||||||
|
// we're not signed in, just go ahead and delete
|
||||||
|
briarController.deleteAccount();
|
||||||
|
// remove from recent apps
|
||||||
|
Intent i = new Intent(this, ExitActivity.class);
|
||||||
|
i.addFlags(FLAG_ACTIVITY_NEW_TASK
|
||||||
|
| FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||||
|
| FLAG_ACTIVITY_NO_ANIMATION
|
||||||
|
| FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
startActivity(i);
|
||||||
|
}, "DeleteAccount");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import static java.lang.System.currentTimeMillis;
|
|||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
|
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
|
||||||
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -65,8 +66,13 @@ public class SplashScreenActivity extends BaseActivity {
|
|||||||
getResources().getInteger(R.integer.splashScreenDuration);
|
getResources().getInteger(R.integer.splashScreenDuration);
|
||||||
new Handler().postDelayed(() -> {
|
new Handler().postDelayed(() -> {
|
||||||
if (currentTimeMillis() >= EXPIRY_DATE) {
|
if (currentTimeMillis() >= EXPIRY_DATE) {
|
||||||
LOG.info("Expired");
|
if (IS_DEBUG_BUILD) {
|
||||||
startNextActivity(ExpiredActivity.class);
|
LOG.info("Expired: debug build");
|
||||||
|
startNextActivity(ExpiredActivity.class);
|
||||||
|
} else {
|
||||||
|
LOG.info("Expired: running on old Android");
|
||||||
|
startNextActivity(ExpiredOldAndroidActivity.class);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
startNextActivity(ENTRY_ACTIVITY);
|
startNextActivity(ENTRY_ACTIVITY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ import androidx.activity.result.contract.ActivityResultContract;
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument;
|
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.GetContent;
|
import androidx.activity.result.contract.ActivityResultContracts.GetContent;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents;
|
import androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_CANCELED;
|
import static android.app.Activity.RESULT_CANCELED;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
||||||
import static android.content.Intent.EXTRA_MIME_TYPES;
|
import static android.content.Intent.EXTRA_MIME_TYPES;
|
||||||
|
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
|
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
|
||||||
|
|
||||||
@@ -23,6 +25,7 @@ import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageConten
|
|||||||
public class ActivityLaunchers {
|
public class ActivityLaunchers {
|
||||||
|
|
||||||
public static class CreateDocumentAdvanced extends CreateDocument {
|
public static class CreateDocumentAdvanced extends CreateDocument {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Intent createIntent(Context context, String input) {
|
public Intent createIntent(Context context, String input) {
|
||||||
Intent i = super.createIntent(context, input);
|
Intent i = super.createIntent(context, input);
|
||||||
@@ -32,20 +35,24 @@ public class ActivityLaunchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class GetContentAdvanced extends GetContent {
|
public static class GetContentAdvanced extends GetContent {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Intent createIntent(Context context, String input) {
|
public Intent createIntent(Context context, String input) {
|
||||||
Intent i = super.createIntent(context, input);
|
Intent i = super.createIntent(context, input);
|
||||||
putShowAdvancedExtra(i);
|
putShowAdvancedExtra(i);
|
||||||
|
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GetImageAdvanced extends GetContent {
|
public static class GetImageAdvanced extends GetContent {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Intent createIntent(Context context, String input) {
|
public Intent createIntent(Context context, String input) {
|
||||||
Intent i = super.createIntent(context, input);
|
Intent i = super.createIntent(context, input);
|
||||||
putShowAdvancedExtra(i);
|
putShowAdvancedExtra(i);
|
||||||
i.setType("image/*");
|
i.setType("image/*");
|
||||||
|
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
if (SDK_INT >= 19)
|
if (SDK_INT >= 19)
|
||||||
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
|
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
|
||||||
return i;
|
return i;
|
||||||
@@ -54,11 +61,13 @@ public class ActivityLaunchers {
|
|||||||
|
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
public static class GetMultipleImagesAdvanced extends GetMultipleContents {
|
public static class GetMultipleImagesAdvanced extends GetMultipleContents {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Intent createIntent(Context context, String input) {
|
public Intent createIntent(Context context, String input) {
|
||||||
Intent i = super.createIntent(context, input);
|
Intent i = super.createIntent(context, input);
|
||||||
putShowAdvancedExtra(i);
|
putShowAdvancedExtra(i);
|
||||||
i.setType("image/*");
|
i.setType("image/*");
|
||||||
|
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
if (SDK_INT >= 19)
|
if (SDK_INT >= 19)
|
||||||
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
|
i.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
|
||||||
return i;
|
return i;
|
||||||
@@ -67,6 +76,7 @@ public class ActivityLaunchers {
|
|||||||
|
|
||||||
public static class RequestBluetoothDiscoverable
|
public static class RequestBluetoothDiscoverable
|
||||||
extends ActivityResultContract<Integer, Boolean> {
|
extends ActivityResultContract<Integer, Boolean> {
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Intent createIntent(Context context, Integer duration) {
|
public Intent createIntent(Context context, Integer duration) {
|
||||||
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ import static java.util.logging.Level.WARNING;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
|
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
|
||||||
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.IS_OLD_ANDROID;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.OLD_ANDROID_WARN_DATE;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
|
||||||
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_INITIAL_COMMENT;
|
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_INITIAL_COMMENT;
|
||||||
@@ -197,6 +199,11 @@ public class UiUtils {
|
|||||||
return DateUtils.formatDateTime(ctx, time, flags);
|
return DateUtils.formatDateTime(ctx, time, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatDateFull(Context ctx, long time) {
|
||||||
|
return DateUtils.formatDateTime(ctx, time,
|
||||||
|
FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given duration in a human-friendly format. For example,
|
* Returns the given duration in a human-friendly format. For example,
|
||||||
* "7 days" or "1 hour 3 minutes".
|
* "7 days" or "1 hour 3 minutes".
|
||||||
@@ -232,6 +239,11 @@ public class UiUtils {
|
|||||||
return (EXPIRY_DATE - now) / DAYS.toMillis(1);
|
return (EXPIRY_DATE - now) / DAYS.toMillis(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean shouldWarnOldAndroidExpiry() {
|
||||||
|
return IS_OLD_ANDROID &&
|
||||||
|
System.currentTimeMillis() >= OLD_ANDROID_WARN_DATE;
|
||||||
|
}
|
||||||
|
|
||||||
public static SpannableStringBuilder getTeaser(Context ctx, Spanned text) {
|
public static SpannableStringBuilder getTeaser(Context ctx, Spanned text) {
|
||||||
if (text.length() < TEASER_LENGTH)
|
if (text.length() < TEASER_LENGTH)
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class AuthorView extends ConstraintLayout {
|
|||||||
if (authorInfo.getStatus() == OURSELVES) {
|
if (authorInfo.getStatus() == OURSELVES) {
|
||||||
authorName.setTypeface(authorNameTypeface, BOLD);
|
authorName.setTypeface(authorNameTypeface, BOLD);
|
||||||
} else {
|
} else {
|
||||||
authorName.setTypeface(authorNameTypeface, NORMAL);
|
authorName.setTypeface(authorNameTypeface, Typeface.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.briar.api.android;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface NetworkUsageMetrics extends Service {
|
||||||
|
|
||||||
|
Metrics getMetrics();
|
||||||
|
|
||||||
|
class Metrics {
|
||||||
|
|
||||||
|
private final long sessionDurationMs, rxBytes, txBytes;
|
||||||
|
|
||||||
|
public Metrics(long sessionDurationMs, long rxBytes,
|
||||||
|
long txBytes) {
|
||||||
|
this.sessionDurationMs = sessionDurationMs;
|
||||||
|
this.rxBytes = rxBytes;
|
||||||
|
this.txBytes = txBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSessionDurationMs() {
|
||||||
|
return sessionDurationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRxBytes() {
|
||||||
|
return rxBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTxBytes() {
|
||||||
|
return txBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_large"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/old_android_expiry_date_reached"
|
||||||
|
android:textSize="@dimen/text_size_large" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/create_new_account"
|
||||||
|
android:textSize="@dimen/text_size_large" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/old_android_delete_account"
|
||||||
|
android:textSize="@dimen/text_size_large" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/delete_account_button"
|
||||||
|
style="@style/BriarButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="@dimen/margin_medium"
|
||||||
|
android:text="@string/delete_account_button" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:keepScreenOn="true">
|
android:keepScreenOn="true">
|
||||||
|
|
||||||
<org.briarproject.briar.android.contact.add.nearby.CameraView
|
<org.briarproject.briar.android.qrcode.CameraView
|
||||||
android:id="@+id/camera_view"
|
android:id="@+id/camera_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
<string name="download_briar">За да продължите да използвате Briar изтеглете последното издание.</string>
|
<string name="download_briar">За да продължите да използвате Briar изтеглете последното издание.</string>
|
||||||
<string name="create_new_account">Ще трябва да създадете нов профил, но ще можете да използвате същия прякор.</string>
|
<string name="create_new_account">Ще трябва да създадете нов профил, но ще можете да използвате същия прякор.</string>
|
||||||
<string name="download_briar_button">Изтегляне</string>
|
<string name="download_briar_button">Изтегляне</string>
|
||||||
|
<string name="delete_account_button">Премахване на профил</string>
|
||||||
<string name="startup_open_database">Хранилището се дешифрира…</string>
|
<string name="startup_open_database">Хранилището се дешифрира…</string>
|
||||||
<string name="startup_migrate_database">Хранилището се обновява…</string>
|
<string name="startup_migrate_database">Хранилището се обновява…</string>
|
||||||
<string name="startup_compact_database">Хранилището се уплътнява…</string>
|
<string name="startup_compact_database">Хранилището се уплътнява…</string>
|
||||||
@@ -280,7 +281,7 @@
|
|||||||
<string name="duplicate_link_dialog_text_1">Вече има чакаща заявка за контакт с тази препратка: %s</string>
|
<string name="duplicate_link_dialog_text_1">Вече има чакаща заявка за контакт с тази препратка: %s</string>
|
||||||
<string name="duplicate_link_dialog_text_1_contact">Вече има контакт с тази препратка: %s</string>
|
<string name="duplicate_link_dialog_text_1_contact">Вече има контакт с тази препратка: %s</string>
|
||||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
<string name="duplicate_link_dialog_text_2">%s и %s един и същи човек ли са?</string>
|
<string name="duplicate_link_dialog_text_2">%1$s и %2$s един и същи човек ли са?</string>
|
||||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||||
@@ -289,7 +290,7 @@
|
|||||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||||
<string name="different_person_button">Не</string>
|
<string name="different_person_button">Не</string>
|
||||||
<string name="duplicate_link_dialog_text_3">%s и %s са изпратили еднакви препратки.\n\nЕдиния от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string>
|
<string name="duplicate_link_dialog_text_3">%1$s и %2$s са изпратили еднакви препратки.\n\nЕдиният от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string>
|
||||||
<string name="pending_contact_updated_toast">Обновена чакаща заявка за контакт</string>
|
<string name="pending_contact_updated_toast">Обновена чакаща заявка за контакт</string>
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">Запознаване на контакти</string>
|
<string name="introduction_onboarding_title">Запознаване на контакти</string>
|
||||||
@@ -510,11 +511,11 @@
|
|||||||
<!--Settings Security and Panic-->
|
<!--Settings Security and Panic-->
|
||||||
<string name="security_settings_title">Сигурност</string>
|
<string name="security_settings_title">Сигурност</string>
|
||||||
<string name="pref_lock_title">Заключване на приложението</string>
|
<string name="pref_lock_title">Заключване на приложението</string>
|
||||||
<string name="pref_lock_summary">Заключва се екрана, за да предпази Briar докато сте вписани</string>
|
<string name="pref_lock_summary">Екранът се заключва, за да се предпази Briar докато сте вписани</string>
|
||||||
<string name="pref_lock_disabled_summary">За да се възползвате от тази възможност, настройте заключване на екрана</string>
|
<string name="pref_lock_disabled_summary">За да се възползвате от тази възможност, настройте заключване на екрана</string>
|
||||||
<string name="pref_lock_timeout_title">Заключване при бездействие</string>
|
<string name="pref_lock_timeout_title">Заключване при бездействие</string>
|
||||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||||
<string name="pref_lock_timeout_summary">При неактивност Briar автоматично се заключав след %s</string>
|
<string name="pref_lock_timeout_summary">При неактивност Briar автоматично се заключва след %s</string>
|
||||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||||
<string name="pref_lock_timeout_1">1 минута</string>
|
<string name="pref_lock_timeout_1">1 минута</string>
|
||||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||||
@@ -534,7 +535,7 @@
|
|||||||
<string name="password_changed">Паролата е променена.</string>
|
<string name="password_changed">Паролата е променена.</string>
|
||||||
<string name="panic_setting">Настройка на бутон за паника</string>
|
<string name="panic_setting">Настройка на бутон за паника</string>
|
||||||
<string name="panic_setting_title">Бутон за паника</string>
|
<string name="panic_setting_title">Бутон за паника</string>
|
||||||
<string name="panic_setting_hint">Настройва се реакцията на Briar при използване на приложение за бутон за паника</string>
|
<string name="panic_setting_hint">Настройва се действието на Briar при използване на приложение за бутон за паника</string>
|
||||||
<string name="panic_app_setting_title">Приложение бутон за паника</string>
|
<string name="panic_app_setting_title">Приложение бутон за паника</string>
|
||||||
<string name="unknown_app">непознато приложение</string>
|
<string name="unknown_app">непознато приложение</string>
|
||||||
<string name="panic_app_setting_summary">Няма зададено приложение</string>
|
<string name="panic_app_setting_summary">Няма зададено приложение</string>
|
||||||
@@ -605,6 +606,7 @@
|
|||||||
<string name="dev_report_memory">Памет</string>
|
<string name="dev_report_memory">Памет</string>
|
||||||
<string name="dev_report_storage">Хранилище</string>
|
<string name="dev_report_storage">Хранилище</string>
|
||||||
<string name="dev_report_connectivity">Свързаност</string>
|
<string name="dev_report_connectivity">Свързаност</string>
|
||||||
|
<string name="dev_report_network_usage">Използване на мрежа</string>
|
||||||
<string name="dev_report_build_config">Настройка на изданието</string>
|
<string name="dev_report_build_config">Настройка на изданието</string>
|
||||||
<string name="dev_report_logcat">Журнал на приложението</string>
|
<string name="dev_report_logcat">Журнал на приложението</string>
|
||||||
<string name="dev_report_device_features">Характеристики</string>
|
<string name="dev_report_device_features">Характеристики</string>
|
||||||
@@ -647,8 +649,8 @@
|
|||||||
<string name="transports_help_text">Briar може да се свърже с контактите ви през интернет, Wi-Fi или Bluetooth.\n\nЗа повече поверителност цялата връзка към интернет се пренасочва през мрежата на Tor.\n\nАко даден контакт може да бъде достъпен чрез няколко метода Briar ги използва успоредно.</string>
|
<string name="transports_help_text">Briar може да се свърже с контактите ви през интернет, Wi-Fi или Bluetooth.\n\nЗа повече поверителност цялата връзка към интернет се пренасочва през мрежата на Tor.\n\nАко даден контакт може да бъде достъпен чрез няколко метода Briar ги използва успоредно.</string>
|
||||||
<!--Share app offline-->
|
<!--Share app offline-->
|
||||||
<string name="hotspot_title">Споделяне на приложението извън мрежа</string>
|
<string name="hotspot_title">Споделяне на приложението извън мрежа</string>
|
||||||
<string name="hotspot_intro">Споделете приложението с някого около вас без достъп до интернет с използване на Wi-Fi на устройствата.
|
<string name="hotspot_intro">Споделете приложението с някого наблизо без използване на интернет, а с през Wi-Fi на устройствата.
|
||||||
\n\nВашето устройство създава безжична точка за достъп. Хората около вас биха могли да се свържат към нея и да изтеглят Briar от вашето устройство.</string>
|
\n\nВашето устройство ще създаде безжична точка за достъп. Хората наблизо биха могли да се свържат към нея и да изтеглят Briar от вашето устройство.</string>
|
||||||
<string name="hotspot_button_start_sharing">Включване на безжична точка</string>
|
<string name="hotspot_button_start_sharing">Включване на безжична точка</string>
|
||||||
<string name="hotspot_button_stop_sharing">Спиране на безжична точка</string>
|
<string name="hotspot_button_stop_sharing">Спиране на безжична точка</string>
|
||||||
<string name="hotspot_progress_text_start">Настройване на безжична точка…</string>
|
<string name="hotspot_progress_text_start">Настройване на безжична точка…</string>
|
||||||
@@ -683,9 +685,21 @@
|
|||||||
<string name="website_download_outro">След като файлът се изтегли, отворете го и го инсталирайте.</string>
|
<string name="website_download_outro">След като файлът се изтегли, отворете го и го инсталирайте.</string>
|
||||||
<string name="website_troubleshooting_title">Отстраняване на неизправности</string>
|
<string name="website_troubleshooting_title">Отстраняване на неизправности</string>
|
||||||
<string name="website_troubleshooting_1">Ако не можете да изтеглите приложението пробвайте с друг мрежов четец.</string>
|
<string name="website_troubleshooting_1">Ако не можете да изтеглите приложението пробвайте с друг мрежов четец.</string>
|
||||||
|
<string name="hotspot_help_wifi_title">Проблеми при свързване чрез Wi-Fi:</string>
|
||||||
|
<string name="hotspot_help_site_title">Проблеми при посещаване на страницата:</string>
|
||||||
<string name="hotspot_help_fallback_title">Нищо не става?</string>
|
<string name="hotspot_help_fallback_title">Нищо не става?</string>
|
||||||
|
<string name="hotspot_help_fallback_intro">Запазете приложението като файл на APK и го споделете по друг начин. След като бъде получен от другото устройство може да бъде използван за инсталиране на Briar.
|
||||||
|
\n\nЗабележка: За да го споделите чрез Bluetooth може да се наложи първо да го преименувате, така че да завършва на .zip.</string>
|
||||||
|
<string name="hotspot_help_fallback_button">Запазване на приложение</string>
|
||||||
<!--error handling-->
|
<!--error handling-->
|
||||||
|
<string name="hotspot_error_intro">Нещо се обърка при споделяне на приложението чрез Wi-Fi:</string>
|
||||||
|
<string name="hotspot_error_no_wifi_direct">Устройството не поддържа Wi-Fi Direct.</string>
|
||||||
|
<string name="hotspot_error_start_callback_failed">Безжичната точка не може да стартира: грешката е %s</string>
|
||||||
|
<string name="hotspot_error_start_callback_failed_unknown">Безжичната точка не може да стартира поради неизвестна грешка: причината е %d</string>
|
||||||
|
<string name="hotspot_error_start_callback_no_group_info">Безжичната точка не може да стартира: няма информация за група</string>
|
||||||
|
<string name="hotspot_error_web_server_start">Грешка при стартиране на уеб сървър</string>
|
||||||
<!--Transfer Data via Removable Drives-->
|
<!--Transfer Data via Removable Drives-->
|
||||||
|
<string name="removable_drive_menu_title">Свързване чрез преносим диск</string>
|
||||||
<string name="removable_drive_title_send">Изпращане на сведения</string>
|
<string name="removable_drive_title_send">Изпращане на сведения</string>
|
||||||
<string name="removable_drive_title_receive">Получаване на сведения</string>
|
<string name="removable_drive_title_receive">Получаване на сведения</string>
|
||||||
<string name="removable_drive_send_intro">Докоснете бутона по-долу, за да бъде създаден файл, който ще съдържа шифрованите съобщения. Можете да изберете къде да бъде запазен този файл.\n\nАко желаете да го запазите на преносим диск го включете сега.</string>
|
<string name="removable_drive_send_intro">Докоснете бутона по-долу, за да бъде създаден файл, който ще съдържа шифрованите съобщения. Можете да изберете къде да бъде запазен този файл.\n\nАко желаете да го запазите на преносим диск го включете сега.</string>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<string name="download_briar">Per continuar utilitzant Briar, baixeu la darrera versió.</string>
|
<string name="download_briar">Per continuar utilitzant Briar, baixeu la darrera versió.</string>
|
||||||
<string name="create_new_account">Haureu de crear un compte nou, però podeu utilitzar el mateix sobrenom.</string>
|
<string name="create_new_account">Haureu de crear un compte nou, però podeu utilitzar el mateix sobrenom.</string>
|
||||||
<string name="download_briar_button">Descarrega la darrera versió</string>
|
<string name="download_briar_button">Descarrega la darrera versió</string>
|
||||||
|
<string name="delete_account_button">Esborreu el compte</string>
|
||||||
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
|
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
|
||||||
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
|
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
|
||||||
<string name="startup_compact_database">S\'està compactant la base de dades...</string>
|
<string name="startup_compact_database">S\'està compactant la base de dades...</string>
|
||||||
@@ -276,7 +277,6 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
|||||||
<string name="duplicate_link_dialog_text_1">Teniu una sol·licitud de contacte pendent amb l\'enllaç %s</string>
|
<string name="duplicate_link_dialog_text_1">Teniu una sol·licitud de contacte pendent amb l\'enllaç %s</string>
|
||||||
<string name="duplicate_link_dialog_text_1_contact">Ja teniu un contacte amb aquest enllaç: %s</string>
|
<string name="duplicate_link_dialog_text_1_contact">Ja teniu un contacte amb aquest enllaç: %s</string>
|
||||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
<string name="duplicate_link_dialog_text_2">%s i %s són la mateixa persona?</string>
|
|
||||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||||
@@ -285,7 +285,6 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
|||||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||||
<string name="different_person_button">Persones diferents</string>
|
<string name="different_person_button">Persones diferents</string>
|
||||||
<string name="duplicate_link_dialog_text_3">%s i %s us han enviat el mateix enllaç.\n\nUn d\'ells pot estar provant de descobrir quins són els vostres contactes.\n\nNo els hi digueu que heu rebut el mateix enllaç d\'algú altre.</string>
|
|
||||||
<string name="pending_contact_updated_toast">S\'ha actualitzat la sol·licitud de contacte pendent</string>
|
<string name="pending_contact_updated_toast">S\'ha actualitzat la sol·licitud de contacte pendent</string>
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">Presenteu als vostres contactes</string>
|
<string name="introduction_onboarding_title">Presenteu als vostres contactes</string>
|
||||||
|
|||||||
@@ -42,21 +42,28 @@
|
|||||||
<string name="dialog_message_lost_password">Dein Briar-Konto ist verschlüsselt auf deinem Gerät und nicht in der Cloud gespeichert, deshalb kannst du dein Passwort nicht zurücksetzen. Willst du dein Konto löschen und neu beginnen?\n\nAchtung: Deine bestehenden Identitäten, Kontakte und Nachrichten gehen dann für immer verloren.</string>
|
<string name="dialog_message_lost_password">Dein Briar-Konto ist verschlüsselt auf deinem Gerät und nicht in der Cloud gespeichert, deshalb kannst du dein Passwort nicht zurücksetzen. Willst du dein Konto löschen und neu beginnen?\n\nAchtung: Deine bestehenden Identitäten, Kontakte und Nachrichten gehen dann für immer verloren.</string>
|
||||||
<string name="startup_failed_activity_title">Fehler beim Starten von Briar</string>
|
<string name="startup_failed_activity_title">Fehler beim Starten von Briar</string>
|
||||||
<string name="startup_failed_clock_error">Briar konnte nicht gestartet werden, weil die Uhr deines Geräts falsch eingestellt ist.\n\Bitte stelle die Uhr deines Geräts auf die richtige Zeit ein und versuche es erneut.</string>
|
<string name="startup_failed_clock_error">Briar konnte nicht gestartet werden, weil die Uhr deines Geräts falsch eingestellt ist.\n\Bitte stelle die Uhr deines Geräts auf die richtige Zeit ein und versuche es erneut.</string>
|
||||||
<string name="startup_failed_db_error">Briar konnte die Datenbank, die dein Konto, deine Kontakte und deine Nachrichten enthält, nicht öffnen.\n\nBitte aktualisiere auf die neueste Version der App und versuche es erneut, oder richte ein neues Konto ein, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string>
|
<string name="startup_failed_db_error">Briar konnte die Datenbank mit deinem Konto, deinen Kontakten und deinen Nachrichten nicht öffnen.\n\nBitte aktualisiere auf die neueste Version der App und versuche es erneut, oder richte ein neues Konto ein, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string>
|
||||||
<string name="startup_failed_data_too_old_error">Dein Konto wurde mit einer alten Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden. Du musst entweder die alte Version neu installieren oder ein neues Konto einrichten, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string>
|
<string name="startup_failed_data_too_old_error">Dein Konto wurde mit einer alten Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden.\n\nDu musst entweder die alte Version neu installieren oder ein neues Konto einrichten, indem du bei der Passwortabfrage \"Ich habe mein Passwort vergessen\" wählst.</string>
|
||||||
<string name="startup_failed_data_too_new_error">Dein Konto wurde mit einer neueren Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden.\n\nBitte aktualisiere auf die aktuelle Version und versuche es erneut.</string>
|
<string name="startup_failed_data_too_new_error">Dein Konto wurde mit einer neueren Version dieser App erstellt und kann mit dieser Version nicht geöffnet werden.\n\nBitte aktualisiere auf die neueste Version und versuche es erneut.</string>
|
||||||
<string name="startup_failed_service_error">Briar konnte eine erforderliche Komponente nicht starten.\n\nBitte aktualisiere auf die neueste Version der App und versuche es erneut.</string>
|
<string name="startup_failed_service_error">Briar konnte eine erforderliche Komponente nicht starten.\n\nBitte aktualisiere auf die neueste Version der App und versuche es erneut.</string>
|
||||||
<plurals name="expiry_warning">
|
<plurals name="expiry_warning">
|
||||||
<item quantity="one">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tag ab und kann nicht verlängert werden.</item>
|
<item quantity="one">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tag ab und kann nicht verlängert werden.</item>
|
||||||
<item quantity="other">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tagen ab und kann nicht verlängert werden.</item>
|
<item quantity="other">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tagen ab und kann nicht verlängert werden.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="old_android_expiry_warning">
|
||||||
|
<item quantity="one">Android 4 wird nicht mehr unterstützt. Briar wird nicht mehr auf %s funktionieren (in %d Tag). Bitte installiere es auf einem neueren Gerät und erstelle ein neues Konto.</item>
|
||||||
|
<item quantity="other">Android 4 wird nicht mehr unterstützt. Briar wird nicht mehr auf %s funktionieren (in %dTagen). Bitte installiere es auf einem neueren Gerät und erstelle ein neues Konto.</item>
|
||||||
|
</plurals>
|
||||||
<string name="expiry_date_reached">Diese Software ist abgelaufen.\nDanke, dass du Briar getestet hast!</string>
|
<string name="expiry_date_reached">Diese Software ist abgelaufen.\nDanke, dass du Briar getestet hast!</string>
|
||||||
<string name="download_briar">Bitte lade die aktuelle Version von Briar herunter, um es weiter zu benutzen.</string>
|
<string name="download_briar">Bitte lade die aktuelle Version von Briar herunter, um es weiter zu benutzen.</string>
|
||||||
<string name="create_new_account">Du wirst ein neues Konto erstellen müssen, wobei du jedoch wieder denselben Benutzernamen verwenden kannst.</string>
|
<string name="create_new_account">Du wirst ein neues Konto erstellen müssen, wobei du jedoch wieder denselben Benutzernamen verwenden kannst.</string>
|
||||||
<string name="download_briar_button">Aktuelle Version herunterladen</string>
|
<string name="download_briar_button">Aktuelle Version herunterladen</string>
|
||||||
|
<string name="old_android_expiry_date_reached">Briar wird auf Android 4 nicht mehr unterstützt.\nBitte installiere Briar auf einem neueren Gerät.</string>
|
||||||
|
<string name="old_android_delete_account">Du kannst auf die Schaltfläche unten tippen, um dein Konto von diesem Gerät zu löschen.</string>
|
||||||
|
<string name="delete_account_button">Konto löschen</string>
|
||||||
<string name="startup_open_database">Datenbank wird entschlüsselt...</string>
|
<string name="startup_open_database">Datenbank wird entschlüsselt...</string>
|
||||||
<string name="startup_migrate_database">Datenbank wird aktualisiert...</string>
|
<string name="startup_migrate_database">Datenbank wird aktualisiert...</string>
|
||||||
<string name="startup_compact_database">Datenbank wird komprimiert ...</string>
|
<string name="startup_compact_database">Datenbank wird komprimiert…</string>
|
||||||
<!--Navigation Drawer-->
|
<!--Navigation Drawer-->
|
||||||
<string name="nav_drawer_open_description">Navigationsleiste öffnen</string>
|
<string name="nav_drawer_open_description">Navigationsleiste öffnen</string>
|
||||||
<string name="nav_drawer_close_description">Navigationsleiste schliessen</string>
|
<string name="nav_drawer_close_description">Navigationsleiste schliessen</string>
|
||||||
@@ -261,7 +268,7 @@
|
|||||||
<string name="adding_contact_failed">Hinzufügen von Kontakt ist fehlgeschlagen</string>
|
<string name="adding_contact_failed">Hinzufügen von Kontakt ist fehlgeschlagen</string>
|
||||||
<string name="dialog_title_remove_pending_contact">Entfernung bestätigen</string>
|
<string name="dialog_title_remove_pending_contact">Entfernung bestätigen</string>
|
||||||
<string name="dialog_message_remove_pending_contact">Dieser Kontakt befindet sich noch beim Hinzufügen. Wenn er jetzt entfernt wird, wird das Hinzufügen abgebrochen.</string>
|
<string name="dialog_message_remove_pending_contact">Dieser Kontakt befindet sich noch beim Hinzufügen. Wenn er jetzt entfernt wird, wird das Hinzufügen abgebrochen.</string>
|
||||||
<string name="own_link_error">Gebe den Link deines Kontakts ein, nicht deinen eigenen.</string>
|
<string name="own_link_error">Gib den Link deines Kontakts ein, nicht deinen eigenen</string>
|
||||||
<string name="nickname_missing">Bitte gib einen Spitznamen an</string>
|
<string name="nickname_missing">Bitte gib einen Spitznamen an</string>
|
||||||
<string name="invalid_link">Ungültiger Link</string>
|
<string name="invalid_link">Ungültiger Link</string>
|
||||||
<string name="unsupported_link">Dieser Link kommt von einer neueren Version von Briar. Bitte führe eine Aktualisierung auf die aktuelle Version durch und versuche es dann nochmal. </string>
|
<string name="unsupported_link">Dieser Link kommt von einer neueren Version von Briar. Bitte führe eine Aktualisierung auf die aktuelle Version durch und versuche es dann nochmal. </string>
|
||||||
@@ -276,11 +283,11 @@
|
|||||||
<item quantity="other">%d neue Kontakte hinzugefügt.</item>
|
<item quantity="other">%d neue Kontakte hinzugefügt.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="offline_state">Keine Internetverbindung</string>
|
<string name="offline_state">Keine Internetverbindung</string>
|
||||||
<string name="duplicate_link_dialog_title">Link duplizieren</string>
|
<string name="duplicate_link_dialog_title">Gleicher Link</string>
|
||||||
<string name="duplicate_link_dialog_text_1">Du hast bereits einen Kontakt mit diesem Link ausstehend: %s</string>
|
<string name="duplicate_link_dialog_text_1">Du hast bereits einen Kontakt mit diesem Link ausstehend: %s</string>
|
||||||
<string name="duplicate_link_dialog_text_1_contact">Du hast bereits einen Kontakt mit diesem Link: %s</string>
|
<string name="duplicate_link_dialog_text_1_contact">Du hast bereits einen Kontakt mit diesem Link: %s</string>
|
||||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
<string name="duplicate_link_dialog_text_2">Sind %s und %s dieselbe Person?</string>
|
<string name="duplicate_link_dialog_text_2">Sind %1$s und %2$s dieselbe Person?</string>
|
||||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||||
@@ -289,7 +296,7 @@
|
|||||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||||
<string name="different_person_button">Andere Person</string>
|
<string name="different_person_button">Andere Person</string>
|
||||||
<string name="duplicate_link_dialog_text_3">%s und %s haben Dir denselben Link geschickt.\n\nMöglicherweise versucht einer der beiden mehr über deine Kontakte zu erfahren.\n\nDu solltest deswegen niemandem sagen, dass du diesen Link auch von jemand anderem erhalten hast.</string>
|
<string name="duplicate_link_dialog_text_3">%1$s und %2$s haben dir denselben Link geschickt.\n\nMöglicherweise versucht einer der beiden mehr über deine Kontakte zu erfahren.\n\nDu solltest deswegen niemandem sagen, dass du diesen Link auch von jemand anderem erhalten hast.</string>
|
||||||
<string name="pending_contact_updated_toast">Ausstehender Kontakt aktualisiert</string>
|
<string name="pending_contact_updated_toast">Ausstehender Kontakt aktualisiert</string>
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">Mache deine Kontakte untereinander bekannt</string>
|
<string name="introduction_onboarding_title">Mache deine Kontakte untereinander bekannt</string>
|
||||||
@@ -307,7 +314,7 @@
|
|||||||
<string name="introduction_request_exists_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. %2$s ist allerdings bereits in deiner Kontaktliste. Da %1$s das vielleicht nicht weiss, kannst du trotzdem antworten:</string>
|
<string name="introduction_request_exists_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. %2$s ist allerdings bereits in deiner Kontaktliste. Da %1$s das vielleicht nicht weiss, kannst du trotzdem antworten:</string>
|
||||||
<string name="introduction_request_answered_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. </string>
|
<string name="introduction_request_answered_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. </string>
|
||||||
<string name="introduction_response_accepted_sent">Du hast die Kontaktempfehlung von %1$s angenommen.</string>
|
<string name="introduction_response_accepted_sent">Du hast die Kontaktempfehlung von %1$s angenommen.</string>
|
||||||
<string name="introduction_response_accepted_sent_info">Bevor %1$s zu deinen Kontakten hinzugefügt werden, müssen sie die Kontaktempfehlung ebenfalls akzeptieren. Dies kann eine Weile dauern.</string>
|
<string name="introduction_response_accepted_sent_info">Bevor %1$s zu deinen Kontakten hinzugefügt wird, muss die Kontaktempfehlung ebenfalls akzeptieren werden. Dies kann eine Weile dauern.</string>
|
||||||
<string name="introduction_response_declined_sent">Du hast die Kontaktempfehlung von %1$s abgelehnt.</string>
|
<string name="introduction_response_declined_sent">Du hast die Kontaktempfehlung von %1$s abgelehnt.</string>
|
||||||
<string name="introduction_response_declined_auto">Die Kontaktempfehlung mit %1$s wurde automatisch abgelehnt.</string>
|
<string name="introduction_response_declined_auto">Die Kontaktempfehlung mit %1$s wurde automatisch abgelehnt.</string>
|
||||||
<string name="introduction_response_accepted_received">%1$s hat die Kontaktempfehlung für %2$s angenommen.</string>
|
<string name="introduction_response_accepted_received">%1$s hat die Kontaktempfehlung für %2$s angenommen.</string>
|
||||||
@@ -326,9 +333,9 @@
|
|||||||
<!--Private Groups-->
|
<!--Private Groups-->
|
||||||
<string name="groups_list_empty">Keine Gruppen vorhanden</string>
|
<string name="groups_list_empty">Keine Gruppen vorhanden</string>
|
||||||
<string name="groups_list_empty_action">Tippe auf das + Symbol, um eine Gruppe anzulegen oder frage deine Kontakte, Gruppen mit dir zu teilen</string>
|
<string name="groups_list_empty_action">Tippe auf das + Symbol, um eine Gruppe anzulegen oder frage deine Kontakte, Gruppen mit dir zu teilen</string>
|
||||||
<string name="groups_created_by">Erstellt durch %s</string>
|
<string name="groups_created_by">Erstellt von %s</string>
|
||||||
<plurals name="messages">
|
<plurals name="messages">
|
||||||
<item quantity="one">%d Nachrichten</item>
|
<item quantity="one">%d Nachricht</item>
|
||||||
<item quantity="other">%d Nachrichten</item>
|
<item quantity="other">%d Nachrichten</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="groups_group_is_empty">Diese Gruppe ist leer</string>
|
<string name="groups_group_is_empty">Diese Gruppe ist leer</string>
|
||||||
@@ -364,7 +371,7 @@
|
|||||||
<item quantity="one">%d offene Gruppeneinladung</item>
|
<item quantity="one">%d offene Gruppeneinladung</item>
|
||||||
<item quantity="other">%d offene Gruppeneinladungen</item>
|
<item quantity="other">%d offene Gruppeneinladungen</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="groups_invitations_response_accepted_sent">Gruppeneinladung von %s angenommen.</string>
|
<string name="groups_invitations_response_accepted_sent">Du hast die Gruppeneinladung von %s angenommen.</string>
|
||||||
<string name="groups_invitations_response_declined_sent">Du hast die Gruppeneinladung abgelehnt von %s abgelehnt.</string>
|
<string name="groups_invitations_response_declined_sent">Du hast die Gruppeneinladung abgelehnt von %s abgelehnt.</string>
|
||||||
<string name="groups_invitations_response_declined_auto">Die Gruppeneinladung von %s wurde automatisch abgelehnt.</string>
|
<string name="groups_invitations_response_declined_auto">Die Gruppeneinladung von %s wurde automatisch abgelehnt.</string>
|
||||||
<string name="groups_invitations_response_accepted_received">%s hat die Gruppeneinladung angenommen.</string>
|
<string name="groups_invitations_response_accepted_received">%s hat die Gruppeneinladung angenommen.</string>
|
||||||
@@ -415,7 +422,7 @@
|
|||||||
<string name="forum_declined_toast">Einladung abgelehnt</string>
|
<string name="forum_declined_toast">Einladung abgelehnt</string>
|
||||||
<string name="shared_by_format">Geteilt durch %s</string>
|
<string name="shared_by_format">Geteilt durch %s</string>
|
||||||
<string name="forum_invitation_already_sharing">Bereits geteilt.</string>
|
<string name="forum_invitation_already_sharing">Bereits geteilt.</string>
|
||||||
<string name="forum_invitation_response_accepted_sent">Du hast die Forumseinladung von %s angenommen.</string>
|
<string name="forum_invitation_response_accepted_sent">Du hast die Forumeinladung von %s angenommen.</string>
|
||||||
<string name="forum_invitation_response_declined_sent">Du hast die Forumeinladung von %s abgelehnt.</string>
|
<string name="forum_invitation_response_declined_sent">Du hast die Forumeinladung von %s abgelehnt.</string>
|
||||||
<string name="forum_invitation_response_declined_auto">Die Forumeinladung von %s wurde automatisch abgelehnt.</string>
|
<string name="forum_invitation_response_declined_auto">Die Forumeinladung von %s wurde automatisch abgelehnt.</string>
|
||||||
<string name="forum_invitation_response_accepted_received">%s hat die Forumeinladung angenommen.</string>
|
<string name="forum_invitation_response_accepted_received">%s hat die Forumeinladung angenommen.</string>
|
||||||
@@ -456,7 +463,7 @@
|
|||||||
<string name="blogs_sharing_response_accepted_received">%s hat die Blogeinladung angenommen.</string>
|
<string name="blogs_sharing_response_accepted_received">%s hat die Blogeinladung angenommen.</string>
|
||||||
<string name="blogs_sharing_response_declined_received">%s hat die Blogeinladung abgelehnt.</string>
|
<string name="blogs_sharing_response_declined_received">%s hat die Blogeinladung abgelehnt.</string>
|
||||||
<string name="blogs_sharing_invitation_received">%1$shat den Blog \"%2$s\" mit dir geteilt.</string>
|
<string name="blogs_sharing_invitation_received">%1$shat den Blog \"%2$s\" mit dir geteilt.</string>
|
||||||
<string name="blogs_sharing_invitation_sent">Du teilst den Blog \"%1$s\" mit %2$s.</string>
|
<string name="blogs_sharing_invitation_sent">Du hast den Blog \"%1$s\" mit %2$s geteilt.</string>
|
||||||
<string name="blogs_sharing_invitations_title">Blogeinladungen</string>
|
<string name="blogs_sharing_invitations_title">Blogeinladungen</string>
|
||||||
<string name="blogs_sharing_joined_toast">Blog abonniert</string>
|
<string name="blogs_sharing_joined_toast">Blog abonniert</string>
|
||||||
<string name="blogs_sharing_declined_toast">Einladung abgelehnt</string>
|
<string name="blogs_sharing_declined_toast">Einladung abgelehnt</string>
|
||||||
@@ -609,6 +616,7 @@
|
|||||||
<string name="dev_report_memory">Speicher</string>
|
<string name="dev_report_memory">Speicher</string>
|
||||||
<string name="dev_report_storage">Speicher</string>
|
<string name="dev_report_storage">Speicher</string>
|
||||||
<string name="dev_report_connectivity">Konnektivität</string>
|
<string name="dev_report_connectivity">Konnektivität</string>
|
||||||
|
<string name="dev_report_network_usage">Netzwerknutzung</string>
|
||||||
<string name="dev_report_build_config">Buildkonfiguration</string>
|
<string name="dev_report_build_config">Buildkonfiguration</string>
|
||||||
<string name="dev_report_logcat">App-Log</string>
|
<string name="dev_report_logcat">App-Log</string>
|
||||||
<string name="dev_report_device_features">Geräteeigenschaften</string>
|
<string name="dev_report_device_features">Geräteeigenschaften</string>
|
||||||
@@ -692,6 +700,7 @@
|
|||||||
<string name="hotspot_help_wifi_title">Probleme bei der WLAN-Verbindung:</string>
|
<string name="hotspot_help_wifi_title">Probleme bei der WLAN-Verbindung:</string>
|
||||||
<string name="hotspot_help_wifi_1">Versuche, WLAN auf beiden Telefonen zu deaktivieren und wieder zu aktivieren, und versuche es dann erneut.</string>
|
<string name="hotspot_help_wifi_1">Versuche, WLAN auf beiden Telefonen zu deaktivieren und wieder zu aktivieren, und versuche es dann erneut.</string>
|
||||||
<string name="hotspot_help_wifi_2">Wenn dein Telefon meldet, dass das WLAN kein Internet hat, sag ihm, dass du trotzdem verbunden bleiben willst.</string>
|
<string name="hotspot_help_wifi_2">Wenn dein Telefon meldet, dass das WLAN kein Internet hat, sag ihm, dass du trotzdem verbunden bleiben willst.</string>
|
||||||
|
<string name="hotspot_help_wifi_3">Telefon neu starten, auf dem der WLAN-Hotspot läuft, danach Briar starten und erneut teilen.</string>
|
||||||
<string name="hotspot_help_site_title">Probleme beim Besuch der lokalen Webseite:</string>
|
<string name="hotspot_help_site_title">Probleme beim Besuch der lokalen Webseite:</string>
|
||||||
<string name="hotspot_help_site_1">Überprüfe unbedingt, ob du die Adresse genau so wie angezeigt eingegeben hast. Ein kleiner Fehler kann dazu führen, dass der Versuch fehlschlägt.</string>
|
<string name="hotspot_help_site_1">Überprüfe unbedingt, ob du die Adresse genau so wie angezeigt eingegeben hast. Ein kleiner Fehler kann dazu führen, dass der Versuch fehlschlägt.</string>
|
||||||
<string name="hotspot_help_site_2">Vergewissere dich, dass dein Telefon immer noch mit dem richtigen WLAN verbunden ist (siehe oben), wenn du die Webseite aufrufen willst.</string>
|
<string name="hotspot_help_site_2">Vergewissere dich, dass dein Telefon immer noch mit dem richtigen WLAN verbunden ist (siehe oben), wenn du die Webseite aufrufen willst.</string>
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
<string name="download_briar">Para continuar usando Briar, por favor descarga la versión mas reciente.</string>
|
<string name="download_briar">Para continuar usando Briar, por favor descarga la versión mas reciente.</string>
|
||||||
<string name="create_new_account">Necesitarás crear una nueva cuenta, pero puedes usar el mismo nombre de usuario.</string>
|
<string name="create_new_account">Necesitarás crear una nueva cuenta, pero puedes usar el mismo nombre de usuario.</string>
|
||||||
<string name="download_briar_button">Descargar la última versión.</string>
|
<string name="download_briar_button">Descargar la última versión.</string>
|
||||||
|
<string name="delete_account_button">Borrar cuenta</string>
|
||||||
<string name="startup_open_database">Descifrando la base de datos...</string>
|
<string name="startup_open_database">Descifrando la base de datos...</string>
|
||||||
<string name="startup_migrate_database">Actualizando la base de datos...</string>
|
<string name="startup_migrate_database">Actualizando la base de datos...</string>
|
||||||
<string name="startup_compact_database">Compactando base de datos...</string>
|
<string name="startup_compact_database">Compactando base de datos...</string>
|
||||||
@@ -280,7 +281,7 @@
|
|||||||
<string name="duplicate_link_dialog_text_1">Ya tiene un contacto pendiente con este enlace: %s</string>
|
<string name="duplicate_link_dialog_text_1">Ya tiene un contacto pendiente con este enlace: %s</string>
|
||||||
<string name="duplicate_link_dialog_text_1_contact">Ya tienes un contacto con este enlace: %s</string>
|
<string name="duplicate_link_dialog_text_1_contact">Ya tienes un contacto con este enlace: %s</string>
|
||||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
<string name="duplicate_link_dialog_text_2">¿Son %s y %s la misma persona?</string>
|
<string name="duplicate_link_dialog_text_2">¿Son %1$s y %2$s la misma persona?</string>
|
||||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||||
@@ -289,7 +290,7 @@
|
|||||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||||
<string name="different_person_button">Diferente Persona</string>
|
<string name="different_person_button">Diferente Persona</string>
|
||||||
<string name="duplicate_link_dialog_text_3">%s y %s le envió el mismo enlace.\n\nUno de ellos puede estar tratando de descubrir quiénes son sus contactos.\n\nNo les diga que recibió el mismo enlace de otra persona.</string>
|
<string name="duplicate_link_dialog_text_3">%1$s y %2$s te enviaron el mismo enlace.\n\nUno de ellos puede estar tratando de descubrir quiénes son tus contactos.\n\nNo les digas que recibiste el mismo enlace de otra persona.</string>
|
||||||
<string name="pending_contact_updated_toast">Contacto pendiente actualizado</string>
|
<string name="pending_contact_updated_toast">Contacto pendiente actualizado</string>
|
||||||
<!--Introductions-->
|
<!--Introductions-->
|
||||||
<string name="introduction_onboarding_title">Presenta a tus contactos</string>
|
<string name="introduction_onboarding_title">Presenta a tus contactos</string>
|
||||||
@@ -609,6 +610,7 @@
|
|||||||
<string name="dev_report_memory">Memoria</string>
|
<string name="dev_report_memory">Memoria</string>
|
||||||
<string name="dev_report_storage">Almacenamiento</string>
|
<string name="dev_report_storage">Almacenamiento</string>
|
||||||
<string name="dev_report_connectivity">Conectividad</string>
|
<string name="dev_report_connectivity">Conectividad</string>
|
||||||
|
<string name="dev_report_network_usage">Uso de red</string>
|
||||||
<string name="dev_report_build_config">Configuración de compilación</string>
|
<string name="dev_report_build_config">Configuración de compilación</string>
|
||||||
<string name="dev_report_logcat">Registro de la aplicación</string>
|
<string name="dev_report_logcat">Registro de la aplicación</string>
|
||||||
<string name="dev_report_device_features">Características del dispositivo</string>
|
<string name="dev_report_device_features">Características del dispositivo</string>
|
||||||
@@ -651,7 +653,7 @@
|
|||||||
<string name="transports_help_text">Briar puede conectar a tus contactos vía Internet, Wi-Fi o Bluetooth.\n\nTodas las conexiones a Internet van a través de la red Tor por privacidad.\n\nSi un contacto puede ser alcanzado por múltiples métodos, Briar los usa en paralelo.</string>
|
<string name="transports_help_text">Briar puede conectar a tus contactos vía Internet, Wi-Fi o Bluetooth.\n\nTodas las conexiones a Internet van a través de la red Tor por privacidad.\n\nSi un contacto puede ser alcanzado por múltiples métodos, Briar los usa en paralelo.</string>
|
||||||
<!--Share app offline-->
|
<!--Share app offline-->
|
||||||
<string name="hotspot_title">Comparte esta aplicación fuera de línea</string>
|
<string name="hotspot_title">Comparte esta aplicación fuera de línea</string>
|
||||||
<string name="hotspot_intro">Comparte esta aplicación con alguien que esté cerca tuyo sin conexión a Internet, usando el Wi-Fi de tu teléfono.
|
<string name="hotspot_intro">Comparte esta aplicación con alguien que esté cerca tuyo sin una conexión a Internet, usando el Wi-Fi de tu teléfono.
|
||||||
\n\nTu teléfono iniciará un punto de acceso Wi-Fi. Las personas cercanas a tí pueden conectarse al punto de acceso y descargar la aplicación Briar desde tu teléfono.</string>
|
\n\nTu teléfono iniciará un punto de acceso Wi-Fi. Las personas cercanas a tí pueden conectarse al punto de acceso y descargar la aplicación Briar desde tu teléfono.</string>
|
||||||
<string name="hotspot_button_start_sharing">Iniciar punto de acceso</string>
|
<string name="hotspot_button_start_sharing">Iniciar punto de acceso</string>
|
||||||
<string name="hotspot_button_stop_sharing">Detener punto de acceso</string>
|
<string name="hotspot_button_stop_sharing">Detener punto de acceso</string>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user