mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
284 Commits
alpha-1.2.
...
removable-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3692b2a97 | ||
|
|
bcbc96dc2d | ||
|
|
1ddcd6cfff | ||
|
|
fd810f5c16 | ||
|
|
3f5e131250 | ||
|
|
3ee516599d | ||
|
|
a5fb3bb4a4 | ||
|
|
eae329cdfa | ||
|
|
0ce0551f0d | ||
|
|
a198e7d08e | ||
|
|
bca6f1506e | ||
|
|
e420201b00 | ||
|
|
03248d04e5 | ||
|
|
2c39b02644 | ||
|
|
c9c6f3682c | ||
|
|
8f4a0ef030 | ||
|
|
5fe22bcd57 | ||
|
|
b4880af7e2 | ||
|
|
51d21bd669 | ||
|
|
b8f3728a0d | ||
|
|
bbfd4f137d | ||
|
|
7e3ca76dd1 | ||
|
|
524c8d26f8 | ||
|
|
7eccf7dac1 | ||
|
|
0bc06248ed | ||
|
|
c999f05cc7 | ||
|
|
428269b312 | ||
|
|
588e05ce83 | ||
|
|
f7875c99b6 | ||
|
|
21fd7f5eed | ||
|
|
6354e91b55 | ||
|
|
8123c06348 | ||
|
|
663c648337 | ||
|
|
bee4e94987 | ||
|
|
c44bdc8762 | ||
|
|
423ecf71d8 | ||
|
|
73c7882cc0 | ||
|
|
683af1ec3a | ||
|
|
77ed15311c | ||
|
|
3d72557618 | ||
|
|
e2a11d42f8 | ||
|
|
0f5ea6ae66 | ||
|
|
5b2c9b85f9 | ||
|
|
94921230d9 | ||
|
|
34788356e6 | ||
|
|
2f719d7f2c | ||
|
|
eb2e8d75f4 | ||
|
|
338d288290 | ||
|
|
03fe1a2d2c | ||
|
|
ade48c7bea | ||
|
|
4a8d89e2bb | ||
|
|
0fed1681ed | ||
|
|
93ad483066 | ||
|
|
d995e73996 | ||
|
|
1d271bab18 | ||
|
|
a9d2aa9366 | ||
|
|
2a48d43e5b | ||
|
|
0a4e23118a | ||
|
|
528a15962f | ||
|
|
0a0cc4e79c | ||
|
|
2b3ba42d70 | ||
|
|
9465331ece | ||
|
|
b0d86bef3e | ||
|
|
a28a2360f1 | ||
|
|
f7a957150e | ||
|
|
67bd065bc3 | ||
|
|
5a3a12767c | ||
|
|
2f86bc312c | ||
|
|
f0fcadfaf4 | ||
|
|
a9d88c849a | ||
|
|
7397efca80 | ||
|
|
17bd6f9a33 | ||
|
|
2d4e7a9fb0 | ||
|
|
0266da993d | ||
|
|
ea5280713f | ||
|
|
0b89d29c7d | ||
|
|
688bac77a8 | ||
|
|
c736bf7c06 | ||
|
|
539730f8ec | ||
|
|
a9c4669c75 | ||
|
|
4c11f93ee2 | ||
|
|
f8dba6fd7f | ||
|
|
419247074f | ||
|
|
2ddb7b5b64 | ||
|
|
c14b59661e | ||
|
|
d70d27e665 | ||
|
|
8991762b0c | ||
|
|
2fc6741c99 | ||
|
|
1075af73f2 | ||
|
|
8671229f76 | ||
|
|
0fb67583ff | ||
|
|
2fc0fd17a2 | ||
|
|
f185860213 | ||
|
|
8ffcdbfc21 | ||
|
|
6a38e2cca8 | ||
|
|
10d9d78ca8 | ||
|
|
03a68038e7 | ||
|
|
fabefcdf4b | ||
|
|
9e4d8ecddf | ||
|
|
1dffbfd8dc | ||
|
|
950db5a87a | ||
|
|
21348d5557 | ||
|
|
eb1a089437 | ||
|
|
17fc81ab7a | ||
|
|
1c54fd1101 | ||
|
|
263bce38cd | ||
|
|
a923c1151c | ||
|
|
0bf10a827f | ||
|
|
49850e4198 | ||
|
|
95b437b311 | ||
|
|
7006f765a6 | ||
|
|
ec3360400c | ||
|
|
5c41d09c52 | ||
|
|
a7590956fd | ||
|
|
a581960121 | ||
|
|
dc57a0b925 | ||
|
|
d6082162ab | ||
|
|
1801afdbb7 | ||
|
|
458add0c9c | ||
|
|
f2374eb141 | ||
|
|
5db5897793 | ||
|
|
f3d628afa7 | ||
|
|
4d3482e40e | ||
|
|
a8cff454ec | ||
|
|
f66cae4749 | ||
|
|
aded1daf92 | ||
|
|
aa1ba0d950 | ||
|
|
071010e438 | ||
|
|
f6d8e364d6 | ||
|
|
f1453ed4c4 | ||
|
|
3a2146cb03 | ||
|
|
24eb76de20 | ||
|
|
693478e0a5 | ||
|
|
bf6be5c5a7 | ||
|
|
a12a639cd3 | ||
|
|
ef37428714 | ||
|
|
644afe8995 | ||
|
|
c66c428124 | ||
|
|
db5b2ea9b6 | ||
|
|
d84603bce2 | ||
|
|
b128370299 | ||
|
|
240e619248 | ||
|
|
c89bde08db | ||
|
|
3ecd1c62b8 | ||
|
|
e3c5497283 | ||
|
|
4bd8ee8ccf | ||
|
|
43b437af92 | ||
|
|
56e0d62597 | ||
|
|
d10e5f025d | ||
|
|
b1a80691db | ||
|
|
049aa61e85 | ||
|
|
7026361234 | ||
|
|
5e30dc5bf4 | ||
|
|
024bfc8ec8 | ||
|
|
04e5e8e4d0 | ||
|
|
7c5d47733f | ||
|
|
b24f2a1818 | ||
|
|
ee6664ce9d | ||
|
|
ab434946b5 | ||
|
|
35e431eb99 | ||
|
|
aa8cddf509 | ||
|
|
c9ede0bfc1 | ||
|
|
6ec9a0f2b2 | ||
|
|
2f86112801 | ||
|
|
c032befe6f | ||
|
|
55eccde031 | ||
|
|
5716820439 | ||
|
|
17d433dd9b | ||
|
|
000812bf6d | ||
|
|
5e2187a877 | ||
|
|
e10b6334f5 | ||
|
|
baa0341727 | ||
|
|
814b2b2582 | ||
|
|
56705bde74 | ||
|
|
dceb38b777 | ||
|
|
9947a6aa1b | ||
|
|
7a3be374c8 | ||
|
|
4ea3ce0e3c | ||
|
|
923185b3f4 | ||
|
|
d91e6c6c1a | ||
|
|
1c93a79448 | ||
|
|
e12ad0cd79 | ||
|
|
8d6bd29b93 | ||
|
|
f941a73999 | ||
|
|
c3057141d8 | ||
|
|
49080cb64c | ||
|
|
27dbe23914 | ||
|
|
d7a2de5817 | ||
|
|
0328aa0630 | ||
|
|
b6cf302131 | ||
|
|
e2a894acd3 | ||
|
|
00ed6d9bb8 | ||
|
|
c9a9734368 | ||
|
|
efc56a8724 | ||
|
|
6e6923b108 | ||
|
|
f459beccdb | ||
|
|
751c5a3245 | ||
|
|
8488499da6 | ||
|
|
96a7e3c425 | ||
|
|
0dcf510466 | ||
|
|
0427b12d52 | ||
|
|
9256c66fcc | ||
|
|
706f4e1c4c | ||
|
|
96debcd616 | ||
|
|
07f20e1e0d | ||
|
|
fee2e503bd | ||
|
|
f9f260bbc1 | ||
|
|
61718192ee | ||
|
|
27893f9cdd | ||
|
|
9b0b80ef04 | ||
|
|
3e1c2df4b1 | ||
|
|
fa745410cc | ||
|
|
a427624e8d | ||
|
|
3798ca1e17 | ||
|
|
113120b3ab | ||
|
|
b10ca5b77f | ||
|
|
f10e3d756a | ||
|
|
9608b974ec | ||
|
|
3b6cc9c633 | ||
|
|
5305dd62d1 | ||
|
|
a066190c60 | ||
|
|
cdae8b35f5 | ||
|
|
6ee57315dd | ||
|
|
64f682146d | ||
|
|
36525fbe9d | ||
|
|
8f628f2d45 | ||
|
|
5e84e5b8b6 | ||
|
|
a64878bd00 | ||
|
|
c53fc5eaa3 | ||
|
|
212751c835 | ||
|
|
fe1c6acebb | ||
|
|
ce47bfe018 | ||
|
|
0ee4ade404 | ||
|
|
e99df2b69e | ||
|
|
db84d07c38 | ||
|
|
db610cfb4c | ||
|
|
5b52417d20 | ||
|
|
8a768cf933 | ||
|
|
bebf3bbc39 | ||
|
|
8a3dd5472b | ||
|
|
f971533a5b | ||
|
|
a12166c13b | ||
|
|
51624a31e3 | ||
|
|
cdc632e1af | ||
|
|
31f87f647e | ||
|
|
dcd37f71d1 | ||
|
|
4ca286b28e | ||
|
|
4f3e4b019a | ||
|
|
62cca1335f | ||
|
|
11a18859fb | ||
|
|
1116a7e125 | ||
|
|
415b315292 | ||
|
|
9818ec2b66 | ||
|
|
95ef061a34 | ||
|
|
aaaf8aa66f | ||
|
|
29965e38d0 | ||
|
|
371d49a213 | ||
|
|
6ed95e145e | ||
|
|
8c025c1173 | ||
|
|
9ce541cc31 | ||
|
|
aa57a4c123 | ||
|
|
58d9deb3b8 | ||
|
|
f0685c4a43 | ||
|
|
484817db08 | ||
|
|
670bf15d31 | ||
|
|
6df1e0fd77 | ||
|
|
ec910cb80f | ||
|
|
372516646d | ||
|
|
72e721b0d3 | ||
|
|
6599093611 | ||
|
|
dceeecf1fe | ||
|
|
ace0b9a3d8 | ||
|
|
7c45c90de9 | ||
|
|
1d44305e34 | ||
|
|
a37af592cd | ||
|
|
7f486eef4c | ||
|
|
bcc0442add | ||
|
|
700f6e05bf | ||
|
|
d8327d6de2 | ||
|
|
5a55b3d7e3 | ||
|
|
bed87ed439 | ||
|
|
6d1f1c7852 | ||
|
|
f6b3bde724 | ||
|
|
94ec22bef8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ local.properties
|
||||
|
||||
# Android Studio
|
||||
.idea/*
|
||||
!.idea/inspectionProfiles/
|
||||
!.idea/runConfigurations/
|
||||
!.idea/codeStyleSettings.xml
|
||||
!.idea/codeStyles
|
||||
|
||||
@@ -5,6 +5,15 @@ stages:
|
||||
- optional_tests
|
||||
- check_reproducibility
|
||||
|
||||
workflow:
|
||||
# when to create a CI pipeline
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
|
||||
when: never # avoids duplicate jobs for branch and MR
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
- if: '$CI_COMMIT_TAG'
|
||||
|
||||
.base-test:
|
||||
before_script:
|
||||
- set -e
|
||||
@@ -25,6 +34,10 @@ test:
|
||||
script:
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: always
|
||||
- when: always
|
||||
|
||||
android test:
|
||||
extends: .base-test
|
||||
@@ -34,17 +47,31 @@ android test:
|
||||
# start emulator first, so it can fail early
|
||||
- start-emulator.sh
|
||||
# run normal and screenshot tests together (exclude Large tests)
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest
|
||||
- ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.package=org.briarproject.briar.android -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest
|
||||
after_script:
|
||||
- adb pull /sdcard/Pictures/screenshots
|
||||
artifacts:
|
||||
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
|
||||
paths:
|
||||
- kernel.log
|
||||
- logcat.txt
|
||||
- briar-android/build/reports/androidTests/connected/flavors/*
|
||||
- screenshots
|
||||
expire_in: 3 days
|
||||
when: on_failure
|
||||
when: manual
|
||||
except:
|
||||
- tags
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
when: on_success
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
changes:
|
||||
- briar-android/**/*
|
||||
when: manual
|
||||
allow_failure: true
|
||||
- if: '$CI_COMMIT_TAG == null'
|
||||
when: manual
|
||||
allow_failure: true
|
||||
retry:
|
||||
max: 1
|
||||
tags:
|
||||
- kvm
|
||||
|
||||
@@ -75,11 +102,15 @@ test_reproducible:
|
||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||
|
||||
manual_tests:
|
||||
bridge test:
|
||||
extends: .optional_tests
|
||||
when: manual
|
||||
except:
|
||||
- tags
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
when: on_success
|
||||
allow_failure: true
|
||||
- if: '$CI_COMMIT_TAG == null'
|
||||
when: manual
|
||||
allow_failure: true
|
||||
|
||||
pre_release_tests:
|
||||
extends: .optional_tests
|
||||
|
||||
1
.idea/dictionaries/briar.xml
generated
1
.idea/dictionaries/briar.xml
generated
@@ -7,6 +7,7 @@
|
||||
<w>encrypter</w>
|
||||
<w>identicon</w>
|
||||
<w>introducee</w>
|
||||
<w>introducees</w>
|
||||
<w>introducer</w>
|
||||
<w>onboarding</w>
|
||||
</words>
|
||||
|
||||
14
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
14
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreObjectMethods" value="true" />
|
||||
<option name="ignoreAnonymousClassMethods" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="true" />
|
||||
<option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="true" />
|
||||
<option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
@@ -6,7 +6,7 @@ apply from: 'witness.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
buildToolsVersion '30.0.3'
|
||||
|
||||
packagingOptions {
|
||||
doNotStrip '**/*.so'
|
||||
@@ -14,9 +14,9 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 10219
|
||||
versionName "1.2.19"
|
||||
targetSdkVersion 30
|
||||
versionCode 10303
|
||||
versionName "1.3.3"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -59,8 +59,8 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class AndroidBluetoothPlugin
|
||||
extends BluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
|
||||
class AndroidBluetoothPlugin extends
|
||||
AbstractBluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AndroidBluetoothPlugin.class.getName());
|
||||
@@ -75,6 +75,7 @@ class AndroidBluetoothPlugin
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
private volatile boolean stopDiscoverAndConnect;
|
||||
|
||||
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||
BluetoothConnectionFactory<BluetoothSocket> connectionFactory,
|
||||
@@ -187,22 +188,40 @@ class AndroidBluetoothPlugin
|
||||
@Nullable
|
||||
DuplexTransportConnection discoverAndConnect(String uuid) {
|
||||
if (adapter == null) return null;
|
||||
for (String address : discoverDevices()) {
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||
return connectTo(address, uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Could not connect to "
|
||||
+ scrubMacAddress(address));
|
||||
if (!discoverSemaphore.tryAcquire()) {
|
||||
LOG.info("Discover already running");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
stopDiscoverAndConnect = false;
|
||||
for (String address : discoverDevices()) {
|
||||
if (stopDiscoverAndConnect) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||
return connectTo(address, uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Could not connect to "
|
||||
+ scrubMacAddress(address));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
discoverSemaphore.release();
|
||||
}
|
||||
LOG.info("Could not connect to any devices");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopDiscoverAndConnect() {
|
||||
stopDiscoverAndConnect = true;
|
||||
adapter.cancelDiscovery();
|
||||
}
|
||||
|
||||
private Collection<String> discoverDevices() {
|
||||
List<String> addresses = new ArrayList<>();
|
||||
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
|
||||
|
||||
@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
@Inject
|
||||
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
AndroidExecutor androidExecutor,
|
||||
AndroidWakeLockManager wakeLockManager,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import android.app.Application;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
|
||||
|
||||
private final Application app;
|
||||
|
||||
AndroidRemovableDrivePlugin(Application app, int maxLatency) {
|
||||
super(maxLatency);
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
return app.getContentResolver().openInputStream(Uri.parse(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||
String uri = p.get(PROP_URI);
|
||||
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
|
||||
return app.getContentResolver().openOutputStream(Uri.parse(uri));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class AndroidRemovableDrivePluginFactory implements
|
||||
SimplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
||||
|
||||
private final Application app;
|
||||
|
||||
@Inject
|
||||
AndroidRemovableDrivePluginFactory(Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return MAX_LATENCY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SimplexPlugin createPlugin(PluginCallback callback) {
|
||||
return new AndroidRemovableDrivePlugin(app, MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
||||
private final Application app;
|
||||
|
||||
@Inject
|
||||
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
EventBus eventBus,
|
||||
BackoffFactory backoffFactory,
|
||||
|
||||
@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
||||
private final File torDirectory;
|
||||
|
||||
@Inject
|
||||
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
Application app,
|
||||
NetworkManager networkManager,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface Consumer<T> {
|
||||
|
||||
void accept(T t);
|
||||
}
|
||||
@@ -9,4 +9,7 @@ public interface FeatureFlags {
|
||||
|
||||
boolean shouldEnableProfilePictures();
|
||||
|
||||
boolean shouldEnableDisappearingMessages();
|
||||
|
||||
boolean shouldEnableConnectViaBluetooth();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.briarproject.bramble.api.cleanup;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An interface for registering a hook with the {@link CleanupManager}
|
||||
* that will be called when a message's cleanup deadline is reached.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface CleanupHook {
|
||||
|
||||
/**
|
||||
* Called when the cleanup deadlines of one or more messages are reached.
|
||||
* <p>
|
||||
* The callee is not required to delete the messages, but the hook won't be
|
||||
* called again for these messages unless another cleanup timer is set (see
|
||||
* {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)}
|
||||
* and {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)}).
|
||||
*/
|
||||
void deleteMessages(Transaction txn, GroupId g,
|
||||
Collection<MessageId> messageIds) throws DbException;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.briarproject.bramble.api.cleanup;
|
||||
|
||||
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
/**
|
||||
* The CleanupManager is responsible for tracking the cleanup deadlines of
|
||||
* messages and passing them to their respective
|
||||
* {@link CleanupHook CleanupHooks} when the deadlines are reached.
|
||||
* <p>
|
||||
* The CleanupManager responds to
|
||||
* {@link CleanupTimerStartedEvent CleanupTimerStartedEvents} broadcast by the
|
||||
* {@link DatabaseComponent}.
|
||||
* <p>
|
||||
* See {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)},
|
||||
* {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)},
|
||||
* {@link DatabaseComponent#stopCleanupTimer(Transaction, MessageId)}.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface CleanupManager {
|
||||
|
||||
/**
|
||||
* When scheduling a cleanup task we overshoot the deadline by this many
|
||||
* milliseconds to reduce the number of tasks that need to be scheduled
|
||||
* when messages have cleanup deadlines that are close together.
|
||||
*/
|
||||
long BATCH_DELAY_MS = 1000;
|
||||
|
||||
/**
|
||||
* Registers a hook to be called when messages are due for cleanup.
|
||||
* This method should be called before
|
||||
* {@link LifecycleManager#startServices(SecretKey)}.
|
||||
*/
|
||||
void registerCleanupHook(ClientId c, int majorVersion,
|
||||
CleanupHook hook);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.briarproject.bramble.api.cleanup.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when a message's cleanup timer is started.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class CleanupTimerStartedEvent extends Event {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final long cleanupDeadline;
|
||||
|
||||
public CleanupTimerStartedEvent(MessageId messageId,
|
||||
long cleanupDeadline) {
|
||||
this.messageId = messageId;
|
||||
this.cleanupDeadline = cleanupDeadline;
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public long getCleanupDeadline() {
|
||||
return cleanupDeadline;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.api.client;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
@@ -16,6 +17,7 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -50,9 +52,11 @@ public interface ClientHelper {
|
||||
BdfDictionary getGroupMetadataAsDictionary(Transaction txn, GroupId g)
|
||||
throws DbException, FormatException;
|
||||
|
||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
||||
BdfDictionary query) throws DbException, FormatException;
|
||||
|
||||
BdfDictionary getMessageMetadataAsDictionary(MessageId m)
|
||||
throws DbException,
|
||||
FormatException;
|
||||
throws DbException, FormatException;
|
||||
|
||||
BdfDictionary getMessageMetadataAsDictionary(Transaction txn, MessageId m)
|
||||
throws DbException, FormatException;
|
||||
@@ -119,4 +123,17 @@ public interface ClientHelper {
|
||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||
BdfDictionary properties) throws FormatException;
|
||||
|
||||
/**
|
||||
* Retrieves the contact ID from the group metadata of the given contact
|
||||
* group.
|
||||
*/
|
||||
ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException, FormatException;
|
||||
|
||||
/**
|
||||
* Stores the given contact ID in the group metadata of the given contact
|
||||
* group.
|
||||
*/
|
||||
void setContactId(Transaction txn, GroupId contactGroupId, ContactId c)
|
||||
throws DbException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.bramble.api.client;
|
||||
|
||||
public interface ContactGroupConstants {
|
||||
|
||||
/**
|
||||
* Group metadata key for associating a contact ID with a contact group.
|
||||
*/
|
||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.briarproject.bramble.api.contact.event;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the alias for a contact changed.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class ContactAliasChangedEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
@Nullable
|
||||
private final String alias;
|
||||
|
||||
public ContactAliasChangedEvent(ContactId contactId,
|
||||
@Nullable String alias) {
|
||||
this.contactId = contactId;
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,18 @@ import javax.annotation.Nullable;
|
||||
@NotNullByDefault
|
||||
public interface DatabaseComponent extends TransactionManager {
|
||||
|
||||
/**
|
||||
* Return value for {@link #getNextCleanupDeadline(Transaction)} if
|
||||
* no messages are scheduled to be deleted.
|
||||
*/
|
||||
long NO_CLEANUP_DEADLINE = -1;
|
||||
|
||||
/**
|
||||
* Return value for {@link #startCleanupTimer(Transaction, MessageId)}
|
||||
* if the cleanup timer was not started.
|
||||
*/
|
||||
long TIMER_NOT_STARTED = -1;
|
||||
|
||||
/**
|
||||
* Opens the database and returns true if the database already existed.
|
||||
*
|
||||
@@ -280,6 +292,16 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
Message getMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the total length, including headers, of any messages that are
|
||||
* eligible to be sent to the given contact via a transport with the given
|
||||
* max latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getMessageBytesToSend(Transaction txn, ContactId c, int maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of all delivered messages in the given group.
|
||||
* <p/>
|
||||
@@ -288,6 +310,16 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any delivered messages in the given group with
|
||||
* metadata that matches all entries in the given query. If the query is
|
||||
* empty, the IDs of all delivered messages are returned.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
||||
Metadata query) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that need to be validated.
|
||||
* <p/>
|
||||
@@ -314,6 +346,15 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
Collection<MessageId> getMessagesToShare(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages of any messages that are due for
|
||||
* deletion, along with their group IDs.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Map<GroupId, Collection<MessageId>> getMessagesToDelete(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the metadata for all delivered messages in the given group.
|
||||
* <p/>
|
||||
@@ -395,6 +436,15 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE}
|
||||
* if no messages are scheduled to be deleted.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextCleanupDeadline(Transaction txn) throws DbException;
|
||||
|
||||
/*
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
@@ -535,6 +585,13 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the cleanup timer duration for the given message. This does not
|
||||
* start the message's cleanup timer.
|
||||
*/
|
||||
void setCleanupTimerDuration(Transaction txn, MessageId m, long duration)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given contact as verified.
|
||||
*/
|
||||
@@ -557,6 +614,12 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
*/
|
||||
void setMessagePermanent(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given message as not shared. This method is only meant for
|
||||
* testing.
|
||||
*/
|
||||
void setMessageNotShared(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given message as shared.
|
||||
*/
|
||||
@@ -599,6 +662,22 @@ public interface DatabaseComponent extends TransactionManager {
|
||||
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Starts the cleanup timer for the given message, if a timer duration
|
||||
* has been set and the timer has not already been started.
|
||||
*
|
||||
* @return The cleanup deadline, or {@link #TIMER_NOT_STARTED} if no
|
||||
* timer duration has been set for this message or its timer has already
|
||||
* been started.
|
||||
*/
|
||||
long startCleanupTimer(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Stops the cleanup timer for the given message, if the timer has been
|
||||
* started.
|
||||
*/
|
||||
void stopCleanupTimer(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
*/
|
||||
|
||||
@@ -57,6 +57,7 @@ public class Author implements Nameable {
|
||||
/**
|
||||
* Returns the author's name.
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
public interface FileConstants {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
public interface RemovableDriveConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.drive");
|
||||
|
||||
String PROP_PATH = "path";
|
||||
String PROP_URI = "uri";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface RemovableDriveManager {
|
||||
|
||||
/**
|
||||
* Returns the currently running reader task for the given contact,
|
||||
* or null if no task is running.
|
||||
*/
|
||||
@Nullable
|
||||
RemovableDriveTask getCurrentReaderTask(ContactId c);
|
||||
|
||||
/**
|
||||
* Returns the currently running writer task for the given contact,
|
||||
* or null if no task is running.
|
||||
*/
|
||||
@Nullable
|
||||
RemovableDriveTask getCurrentWriterTask(ContactId c);
|
||||
|
||||
/**
|
||||
* Starts and returns a reader task for the given contact, reading from
|
||||
* a stream described by the given transport properties. If a reader task
|
||||
* for the contact is already running, it will be returned and the
|
||||
* transport properties will be ignored.
|
||||
*/
|
||||
RemovableDriveTask startReaderTask(ContactId c, TransportProperties p);
|
||||
|
||||
/**
|
||||
* Starts and returns a writer task for the given contact, writing to
|
||||
* a stream described by the given transport properties. If a writer task
|
||||
* for the contact is already running, it will be returned and the
|
||||
* transport properties will be ignored.
|
||||
*/
|
||||
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.briarproject.bramble.api.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface RemovableDriveTask extends Runnable {
|
||||
|
||||
/**
|
||||
* Returns the {@link TransportProperties} that were used for creating
|
||||
* this task.
|
||||
*/
|
||||
TransportProperties getTransportProperties();
|
||||
|
||||
/**
|
||||
* Adds an observer to the task. The observer will be notified of state
|
||||
* changes on the event thread. If the task has already finished, the
|
||||
* observer will be notified of its final state.
|
||||
*/
|
||||
void addObserver(Consumer<State> observer);
|
||||
|
||||
/**
|
||||
* Removes an observer from the task.
|
||||
*/
|
||||
void removeObserver(Consumer<State> observer);
|
||||
|
||||
class State {
|
||||
|
||||
private final long done, total;
|
||||
private final boolean finished, success;
|
||||
|
||||
public State(long done, long total, boolean finished, boolean success) {
|
||||
this.done = done;
|
||||
this.total = total;
|
||||
this.finished = finished;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total length in bytes of the messages read or written
|
||||
* so far.
|
||||
*/
|
||||
public long getDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total length in bytes of the messages that will have
|
||||
* been read or written when the task is complete, or zero if the
|
||||
* total is unknown.
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,6 @@ public interface ReportingConstants {
|
||||
* Hidden service address for reporting crashes and feedback to the
|
||||
* developers.
|
||||
*/
|
||||
String DEV_ONION_ADDRESS = "cwqmubyvnig3wag3.onion";
|
||||
String DEV_ONION_ADDRESS =
|
||||
"b2nkt5doeamvdmjzfz7g42hk5vdtlnktlgzhel2bgjcc4v4jhnx2qrqd.onion";
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event {
|
||||
|
||||
private final ContactId contactId;
|
||||
private final Collection<MessageId> messageIds;
|
||||
private final long totalLength;
|
||||
|
||||
public MessagesSentEvent(ContactId contactId,
|
||||
Collection<MessageId> messageIds) {
|
||||
Collection<MessageId> messageIds, long totalLength) {
|
||||
this.contactId = contactId;
|
||||
this.messageIds = messageIds;
|
||||
this.totalLength = totalLength;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
@@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event {
|
||||
public Collection<MessageId> getMessageIds() {
|
||||
return messageIds;
|
||||
}
|
||||
|
||||
public long getTotalLength() {
|
||||
return totalLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class ValidationUtils {
|
||||
|
||||
@@ -64,4 +66,9 @@ public class ValidationUtils {
|
||||
if (dictionary != null && dictionary.size() != size)
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
public static void checkRange(@Nullable Long l, long min, long max)
|
||||
throws FormatException {
|
||||
if (l != null && (l < min || l > max)) throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
public interface TimeTravel {
|
||||
|
||||
void setCurrentTimeMillis(long now) throws InterruptedException;
|
||||
|
||||
void addCurrentTimeMillis(long add) throws InterruptedException;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.cleanup.CleanupModule;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
@@ -14,6 +15,8 @@ import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
public interface BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(CleanupModule.EagerSingletons init);
|
||||
|
||||
void inject(ContactModule.EagerSingletons init);
|
||||
|
||||
void inject(CryptoExecutorModule.EagerSingletons init);
|
||||
@@ -39,6 +42,7 @@ public interface BrambleCoreEagerSingletons {
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(BrambleCoreEagerSingletons c) {
|
||||
c.inject(new CleanupModule.EagerSingletons());
|
||||
c.inject(new ContactModule.EagerSingletons());
|
||||
c.inject(new CryptoExecutorModule.EagerSingletons());
|
||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.cleanup.CleanupModule;
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.connection.ConnectionModule;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
@@ -21,15 +22,14 @@ import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||
import org.briarproject.bramble.settings.SettingsModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||
import org.briarproject.bramble.system.ClockModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.bramble.versioning.VersioningModule;
|
||||
|
||||
import dagger.Module;
|
||||
|
||||
@Module(includes = {
|
||||
CleanupModule.class,
|
||||
ClientModule.class,
|
||||
ClockModule.class,
|
||||
ConnectionModule.class,
|
||||
ContactModule.class,
|
||||
CryptoModule.class,
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
package org.briarproject.bramble.cleanup;
|
||||
|
||||
import org.briarproject.bramble.api.cleanup.CleanupHook;
|
||||
import org.briarproject.bramble.api.cleanup.CleanupManager;
|
||||
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class CleanupManagerImpl implements CleanupManager, Service, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(CleanupManagerImpl.class.getName());
|
||||
|
||||
private final Executor dbExecutor;
|
||||
private final DatabaseComponent db;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final Clock clock;
|
||||
private final Map<ClientMajorVersion, CleanupHook> hooks =
|
||||
new ConcurrentHashMap<>();
|
||||
private final Object lock = new Object();
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final Set<CleanupTask> pending = new HashSet<>();
|
||||
|
||||
@Inject
|
||||
CleanupManagerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
DatabaseComponent db, TaskScheduler taskScheduler, Clock clock) {
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.db = db;
|
||||
this.taskScheduler = taskScheduler;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCleanupHook(ClientId c, int majorVersion,
|
||||
CleanupHook hook) {
|
||||
hooks.put(new ClientMajorVersion(c, majorVersion), hook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() {
|
||||
maybeScheduleTask(clock.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof CleanupTimerStartedEvent) {
|
||||
CleanupTimerStartedEvent a = (CleanupTimerStartedEvent) e;
|
||||
maybeScheduleTask(a.getCleanupDeadline());
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeScheduleTask(long deadline) {
|
||||
synchronized (lock) {
|
||||
for (CleanupTask task : pending) {
|
||||
if (task.deadline <= deadline) return;
|
||||
}
|
||||
CleanupTask task = new CleanupTask(deadline);
|
||||
pending.add(task);
|
||||
scheduleTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleTask(CleanupTask task) {
|
||||
long now = clock.currentTimeMillis();
|
||||
long delay = max(0, task.deadline - now + BATCH_DELAY_MS);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Scheduling cleanup task in " + delay + " ms");
|
||||
}
|
||||
taskScheduler.schedule(() -> deleteMessagesAndScheduleNextTask(task),
|
||||
dbExecutor, delay, MILLISECONDS);
|
||||
}
|
||||
|
||||
private void deleteMessagesAndScheduleNextTask(CleanupTask task) {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
pending.remove(task);
|
||||
}
|
||||
long deadline = db.transactionWithResult(false, txn -> {
|
||||
deleteMessages(txn);
|
||||
return db.getNextCleanupDeadline(txn);
|
||||
});
|
||||
if (deadline != NO_CLEANUP_DEADLINE) {
|
||||
maybeScheduleTask(deadline);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteMessages(Transaction txn) throws DbException {
|
||||
Map<GroupId, Collection<MessageId>> ids = db.getMessagesToDelete(txn);
|
||||
for (Entry<GroupId, Collection<MessageId>> e : ids.entrySet()) {
|
||||
GroupId groupId = e.getKey();
|
||||
Collection<MessageId> messageIds = e.getValue();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(messageIds.size() + " messages to delete");
|
||||
}
|
||||
for (MessageId m : messageIds) db.stopCleanupTimer(txn, m);
|
||||
Group group = db.getGroup(txn, groupId);
|
||||
ClientMajorVersion cv = new ClientMajorVersion(group.getClientId(),
|
||||
group.getMajorVersion());
|
||||
CleanupHook hook = hooks.get(cv);
|
||||
if (hook == null) {
|
||||
throw new IllegalStateException("No cleanup hook for " + cv);
|
||||
}
|
||||
hook.deleteMessages(txn, groupId, messageIds);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CleanupTask {
|
||||
|
||||
private final long deadline;
|
||||
|
||||
private CleanupTask(long deadline) {
|
||||
this.deadline = deadline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.briarproject.bramble.cleanup;
|
||||
|
||||
import org.briarproject.bramble.api.cleanup.CleanupManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class CleanupModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
CleanupManager cleanupManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CleanupManager provideCleanupManager(LifecycleManager lifecycleManager,
|
||||
EventBus eventBus, CleanupManagerImpl cleanupManager) {
|
||||
lifecycleManager.registerService(cleanupManager);
|
||||
eventBus.addListener(cleanupManager);
|
||||
return cleanupManager;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package org.briarproject.bramble.client;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
@@ -32,6 +34,7 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -39,6 +42,7 @@ import java.util.Map.Entry;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
@@ -151,6 +155,12 @@ class ClientHelperImpl implements ClientHelper {
|
||||
return metadataParser.parse(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
||||
BdfDictionary query) throws DbException, FormatException {
|
||||
return db.getMessageIds(txn, g, metadataEncoder.encode(query));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary getMessageMetadataAsDictionary(MessageId m)
|
||||
throws DbException, FormatException {
|
||||
@@ -389,4 +399,27 @@ class ClientHelperImpl implements ClientHelper {
|
||||
return tpMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
|
||||
throws DbException {
|
||||
try {
|
||||
BdfDictionary meta =
|
||||
getGroupMetadataAsDictionary(txn, contactGroupId);
|
||||
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e); // Invalid group metadata
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactId(Transaction txn, GroupId contactGroupId,
|
||||
ContactId c) throws DbException {
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_CONTACT_ID, c.getInt()));
|
||||
try {
|
||||
mergeGroupMetadata(txn, contactGroupId, meta);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,13 @@ interface Database<T> {
|
||||
*/
|
||||
void close() throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the dirty flag was set while opening the database,
|
||||
* indicating that the database has not been shut down properly the last
|
||||
* time it was closed and some data could be lost.
|
||||
*/
|
||||
boolean wasDirtyOnInitialisation();
|
||||
|
||||
/**
|
||||
* Starts a new transaction and returns an object representing it.
|
||||
*/
|
||||
@@ -340,6 +347,16 @@ interface Database<T> {
|
||||
*/
|
||||
Message getMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the total length, including headers, of any messages that are
|
||||
* eligible to be sent to the given contact via a transport with the given
|
||||
* max latency.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getMessageBytesToSend(T txn, ContactId c, int maxLatency)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs and states of all dependencies of the given message.
|
||||
* For missing dependencies and dependencies in other groups, the state
|
||||
@@ -497,6 +514,25 @@ interface Database<T> {
|
||||
*/
|
||||
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages of any messages that are due for
|
||||
* deletion, along with their group IDs.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Map<GroupId, Collection<MessageId>> getMessagesToDelete(T txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be deleted, or
|
||||
* {@link DatabaseComponent#NO_CLEANUP_DEADLINE} if no messages are
|
||||
* scheduled to be deleted.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextCleanupDeadline(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
@@ -606,8 +642,10 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Marks a message as having been seen by the given contact.
|
||||
*
|
||||
* @return True if the message was not already marked as seen.
|
||||
*/
|
||||
void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
|
||||
boolean raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a contact from the database.
|
||||
@@ -671,6 +709,13 @@ interface Database<T> {
|
||||
*/
|
||||
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the cleanup timer duration for the given message. This does not
|
||||
* start the message's cleanup timer.
|
||||
*/
|
||||
void setCleanupTimerDuration(T txn, MessageId m, long duration)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given contact as verified.
|
||||
*/
|
||||
@@ -701,9 +746,10 @@ interface Database<T> {
|
||||
void setMessagePermanent(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given message as shared.
|
||||
* Marks the given message as shared or not.
|
||||
*/
|
||||
void setMessageShared(T txn, MessageId m) throws DbException;
|
||||
void setMessageShared(T txn, MessageId m, boolean shared)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the validation and delivery state of the given message.
|
||||
@@ -730,6 +776,22 @@ interface Database<T> {
|
||||
void setTransportKeysActive(T txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Starts the cleanup timer for the given message, if a timer duration
|
||||
* has been set and the timer has not already been started.
|
||||
*
|
||||
* @return The cleanup deadline, or
|
||||
* {@link DatabaseComponent#TIMER_NOT_STARTED} if no timer duration has
|
||||
* been set for this message or its timer has already been started.
|
||||
*/
|
||||
long startCleanupTimer(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Stops the cleanup timer for the given message, if the timer has been
|
||||
* started.
|
||||
*/
|
||||
void stopCleanupTimer(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the transmission count, expiry time and estimated time of arrival
|
||||
* of the given message with respect to the given contact, using the latency
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
|
||||
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
|
||||
@@ -413,14 +415,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getMessage(txn, m));
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
transaction.attach(new MessagesSentEvent(c, ids));
|
||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -465,14 +470,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids =
|
||||
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
||||
long totalLength = 0;
|
||||
List<Message> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getMessage(txn, m));
|
||||
Message message = db.getMessage(txn, m);
|
||||
totalLength += message.getRawLength();
|
||||
messages.add(message);
|
||||
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||
}
|
||||
if (ids.isEmpty()) return null;
|
||||
db.lowerRequestedFlag(txn, c, ids);
|
||||
transaction.attach(new MessagesSentEvent(c, ids));
|
||||
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -567,6 +575,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessage(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMessageBytesToSend(Transaction transaction, ContactId c,
|
||||
int maxLatency) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
return db.getMessageBytesToSend(txn, c, maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessageIds(Transaction transaction,
|
||||
GroupId g) throws DbException {
|
||||
@@ -576,6 +593,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessageIds(txn, g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessageIds(Transaction transaction,
|
||||
GroupId g, Metadata query) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsGroup(txn, g))
|
||||
throw new NoSuchGroupException();
|
||||
return db.getMessageIds(txn, g, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||
throws DbException {
|
||||
@@ -597,6 +623,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessagesToShare(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<GroupId, Collection<MessageId>> getMessagesToDelete(
|
||||
Transaction transaction) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getMessagesToDelete(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<MessageId, Metadata> getMessageMetadata(Transaction transaction,
|
||||
GroupId g) throws DbException {
|
||||
@@ -692,6 +725,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessageDependents(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextCleanupDeadline(Transaction transaction)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextCleanupDeadline(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
@@ -795,8 +835,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Collection<MessageId> acked = new ArrayList<>();
|
||||
for (MessageId m : a.getMessageIds()) {
|
||||
if (db.containsVisibleMessage(txn, c, m)) {
|
||||
db.raiseSeenFlag(txn, c, m);
|
||||
acked.add(m);
|
||||
if (db.raiseSeenFlag(txn, c, m)) {
|
||||
// This is the first time the message has been acked by
|
||||
// this contact. Start the cleanup timer (a no-op unless
|
||||
// a cleanup deadline has been set for this message)
|
||||
long deadline = db.startCleanupTimer(txn, m);
|
||||
if (deadline != TIMER_NOT_STARTED) {
|
||||
transaction.attach(new CleanupTimerStartedEvent(m,
|
||||
deadline));
|
||||
}
|
||||
acked.add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (acked.size() > 0) {
|
||||
@@ -952,6 +1001,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.removeTransportKeys(txn, t, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
|
||||
long duration) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
db.setCleanupTimerDuration(txn, m, duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactVerified(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
@@ -970,6 +1029,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
transaction.attach(new ContactAliasChangedEvent(c, alias));
|
||||
db.setContactAlias(txn, c, alias);
|
||||
}
|
||||
|
||||
@@ -1001,6 +1061,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.setMessagePermanent(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageNotShared(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
db.setMessageShared(txn, m, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageShared(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
@@ -1010,7 +1080,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchMessageException();
|
||||
if (db.getMessageState(txn, m) != DELIVERED)
|
||||
throw new IllegalArgumentException("Shared undelivered message");
|
||||
db.setMessageShared(txn, m);
|
||||
db.setMessageShared(txn, m, true);
|
||||
transaction.attach(new MessageSharedEvent(m));
|
||||
}
|
||||
|
||||
@@ -1082,6 +1152,30 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.setTransportKeysActive(txn, t, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long startCleanupTimer(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
long deadline = db.startCleanupTimer(txn, m);
|
||||
if (deadline != TIMER_NOT_STARTED) {
|
||||
transaction.attach(new CleanupTimerStartedEvent(m, deadline));
|
||||
}
|
||||
return deadline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopCleanupTimer(Transaction transaction, MessageId m)
|
||||
throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
db.stopCleanupTimer(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportKeys(Transaction transaction,
|
||||
Collection<TransportKeySet> keys) throws DbException {
|
||||
|
||||
@@ -37,4 +37,10 @@ interface DatabaseConstants {
|
||||
* has passed since the last compaction.
|
||||
*/
|
||||
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the flag is stored indicating
|
||||
* whether the database is marked as dirty.
|
||||
*/
|
||||
String DIRTY_KEY = "dirty";
|
||||
}
|
||||
|
||||
@@ -84,9 +84,14 @@ class H2Database extends JdbcDatabase {
|
||||
@Override
|
||||
public void close() throws DbException {
|
||||
// H2 will close the database when the last connection closes
|
||||
Connection c = null;
|
||||
try {
|
||||
c = createConnection();
|
||||
super.closeAllConnections();
|
||||
setDirty(c, false);
|
||||
c.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(c, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
||||
try {
|
||||
super.closeAllConnections();
|
||||
c = createConnection();
|
||||
setDirty(c, false);
|
||||
s = c.createStatement();
|
||||
s.executeQuery("SHUTDOWN");
|
||||
s.close();
|
||||
|
||||
@@ -72,6 +72,8 @@ import static java.util.Arrays.asList;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
|
||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@@ -81,6 +83,7 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||
@@ -98,7 +101,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 47;
|
||||
static final int CODE_SCHEMA_VERSION = 48;
|
||||
|
||||
// Time period offsets for incoming transport keys
|
||||
private static final int OFFSET_PREV = -1;
|
||||
@@ -180,6 +183,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " state INT NOT NULL,"
|
||||
+ " shared BOOLEAN NOT NULL,"
|
||||
+ " temporary BOOLEAN NOT NULL,"
|
||||
// Null if no timer duration has been set
|
||||
+ " cleanupTimerDuration BIGINT,"
|
||||
// Null if no timer duration has been set or the timer
|
||||
// hasn't started
|
||||
+ " cleanupDeadline BIGINT,"
|
||||
+ " length INT NOT NULL,"
|
||||
+ " raw BLOB," // Null if message has been deleted
|
||||
+ " PRIMARY KEY (messageId),"
|
||||
@@ -336,6 +344,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
||||
+ " ON statuses (contactId, timestamp)";
|
||||
|
||||
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
|
||||
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
||||
+ " ON messages (cleanupDeadline)";
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(JdbcDatabase.class.getName());
|
||||
|
||||
@@ -354,9 +366,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
@GuardedBy("connectionsLock")
|
||||
private boolean closed = false;
|
||||
|
||||
private volatile boolean wasDirtyOnInitialisation = false;
|
||||
|
||||
protected abstract Connection createConnection()
|
||||
throws DbException, SQLException;
|
||||
|
||||
// Used exclusively during open to compact the database after schema
|
||||
// migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
|
||||
// elapsed
|
||||
protected abstract void compactAndClose() throws DbException;
|
||||
|
||||
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||
@@ -381,13 +398,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
if (reopen) {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
wasDirtyOnInitialisation = isDirty(s);
|
||||
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
|
||||
} else {
|
||||
wasDirtyOnInitialisation = false;
|
||||
createTables(txn);
|
||||
initialiseSettings(txn);
|
||||
compact = false;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("db dirty? " + wasDirtyOnInitialisation);
|
||||
}
|
||||
createIndexes(txn);
|
||||
setDirty(txn, true);
|
||||
commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
abortTransaction(txn);
|
||||
@@ -414,6 +437,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasDirtyOnInitialisation() {
|
||||
return wasDirtyOnInitialisation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the schema version stored in the database with the schema
|
||||
* version used by the current code and applies any suitable migrations to
|
||||
@@ -463,7 +491,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
new Migration43_44(dbTypes),
|
||||
new Migration44_45(),
|
||||
new Migration45_46(),
|
||||
new Migration46_47(dbTypes)
|
||||
new Migration46_47(dbTypes),
|
||||
new Migration47_48()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -488,6 +517,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
private boolean isDirty(Settings s) {
|
||||
return s.getBoolean(DIRTY_KEY, false);
|
||||
}
|
||||
|
||||
protected void setDirty(Connection txn, boolean dirty) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(DIRTY_KEY, dirty);
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
private void initialiseSettings(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
||||
@@ -531,6 +570,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
@@ -1290,7 +1330,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
public void deleteMessage(Connection txn, MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE messages SET raw = NULL WHERE messageId = ?";
|
||||
String sql = "UPDATE messages"
|
||||
+ " SET raw = NULL, cleanupDeadline = NULL"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
@@ -1716,6 +1758,37 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMessageBytesToSend(Connection txn, ContactId c,
|
||||
int maxLatency) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long eta = now + maxLatency;
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT SUM(length) FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " AND (expiry <= ? OR eta > ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
ps.setLong(4, eta);
|
||||
rs = ps.executeQuery();
|
||||
rs.next();
|
||||
long total = rs.getInt(1);
|
||||
rs.close();
|
||||
ps.close();
|
||||
return total;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessageIds(Connection txn, GroupId g)
|
||||
throws DbException {
|
||||
@@ -1769,7 +1842,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
// Return early if there are no matches
|
||||
if (intersection.isEmpty()) return Collections.emptySet();
|
||||
}
|
||||
if (intersection == null) throw new AssertionError();
|
||||
return intersection;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
@@ -2226,6 +2298,39 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<GroupId, Collection<MessageId>> getMessagesToDelete(
|
||||
Connection txn) throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId, groupId FROM messages"
|
||||
+ " WHERE cleanupDeadline <= ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setLong(1, now);
|
||||
rs = ps.executeQuery();
|
||||
Map<GroupId, Collection<MessageId>> ids = new HashMap<>();
|
||||
while (rs.next()) {
|
||||
MessageId m = new MessageId(rs.getBytes(1));
|
||||
GroupId g = new GroupId(rs.getBytes(2));
|
||||
Collection<MessageId> messageIds = ids.get(g);
|
||||
if (messageIds == null) {
|
||||
messageIds = new ArrayList<>();
|
||||
ids.put(g, messageIds);
|
||||
}
|
||||
messageIds.add(m);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return ids;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
@@ -2256,6 +2361,31 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextCleanupDeadline(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT cleanupDeadline FROM messages"
|
||||
+ " WHERE cleanupDeadline IS NOT NULL"
|
||||
+ " ORDER BY cleanupDeadline LIMIT 1";
|
||||
s = txn.createStatement();
|
||||
rs = s.executeQuery(sql);
|
||||
long nextDeadline = NO_CLEANUP_DEADLINE;
|
||||
if (rs.next()) {
|
||||
nextDeadline = rs.getLong(1);
|
||||
if (rs.next()) throw new AssertionError();
|
||||
}
|
||||
rs.close();
|
||||
s.close();
|
||||
return nextDeadline;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(s, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PendingContact getPendingContact(Connection txn, PendingContactId p)
|
||||
throws DbException {
|
||||
@@ -2776,7 +2906,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raiseSeenFlag(Connection txn, ContactId c, MessageId m)
|
||||
public boolean raiseSeenFlag(Connection txn, ContactId c, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
@@ -2788,6 +2918,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
return affected == 1;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
@@ -3021,6 +3152,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCleanupTimerDuration(Connection txn, MessageId m,
|
||||
long duration) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE messages SET cleanupTimerDuration = ?"
|
||||
+ " WHERE messageId = ? AND cleanupTimerDuration IS NULL";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setLong(1, duration);
|
||||
ps.setBytes(2, m.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactVerified(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
@@ -3128,22 +3278,24 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageShared(Connection txn, MessageId m)
|
||||
public void setMessageShared(Connection txn, MessageId m, boolean shared)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE messages SET shared = TRUE"
|
||||
String sql = "UPDATE messages SET shared = ?"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setBoolean(1, shared);
|
||||
ps.setBytes(2, m.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET messageShared = TRUE"
|
||||
sql = "UPDATE statuses SET messageShared = ?"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setBoolean(1, shared);
|
||||
ps.setBytes(2, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -3272,6 +3424,60 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long startCleanupTimer(Connection txn, MessageId m)
|
||||
throws DbException {
|
||||
long now = clock.currentTimeMillis();
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "UPDATE messages"
|
||||
+ " SET cleanupDeadline = ? + cleanupTimerDuration"
|
||||
+ " WHERE messageId = ?"
|
||||
+ " AND cleanupTimerDuration IS NOT NULL"
|
||||
+ " AND cleanupDeadline IS NULL";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setLong(1, now);
|
||||
ps.setBytes(2, m.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
if (affected == 0) return TIMER_NOT_STARTED;
|
||||
sql = "SELECT cleanupDeadline FROM messages WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
long deadline = rs.getLong(1);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return deadline;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs, LOG, WARNING);
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopCleanupTimer(Connection txn, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE messages SET cleanupDeadline = NULL"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
|
||||
int maxLatency) throws DbException {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||
|
||||
class Migration47_48 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG = getLogger(Migration47_48.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 47;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 48;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
// Null if no timer duration has been set
|
||||
s.execute("ALTER TABLE messages"
|
||||
+ " ADD COLUMN cleanupTimerDuration BIGINT");
|
||||
// Null if no timer duration has been set or the timer
|
||||
// hasn't started
|
||||
s.execute("ALTER TABLE messages"
|
||||
+ " ADD COLUMN cleanupDeadline BIGINT");
|
||||
s.execute("CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
||||
+ " ON messages (cleanupDeadline)");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s, LOG, WARNING);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
|
||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||
|
||||
// Accept records with current protocol version, known record type
|
||||
private static Predicate<Record> ACCEPT = r ->
|
||||
private static final Predicate<Record> ACCEPT = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
isKnownRecordType(r.getRecordType());
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static Predicate<Record> IGNORE = r ->
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Multiset;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionHandler;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.event.RemoteTransportPropertiesUpdatedEvent;
|
||||
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
|
||||
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_ADDRESS_IS_REFLECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_EVER_CONNECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_ADDRESS_IS_REFLECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_EVER_CONNECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.bramble.util.StringUtils.macToBytes;
|
||||
import static org.briarproject.bramble.util.StringUtils.macToString;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
|
||||
EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AbstractBluetoothPlugin.class.getName());
|
||||
|
||||
private final BluetoothConnectionLimiter connectionLimiter;
|
||||
final BluetoothConnectionFactory<S> connectionFactory;
|
||||
|
||||
private final Executor ioExecutor, wakefulIoExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
private final PluginCallback callback;
|
||||
private final int maxLatency, maxIdleTime;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
private final AtomicBoolean everConnected = new AtomicBoolean(false);
|
||||
|
||||
protected final PluginState state = new PluginState();
|
||||
protected final Semaphore discoverSemaphore = new Semaphore(1);
|
||||
|
||||
private volatile String contactConnectionsUuid = null;
|
||||
|
||||
abstract void initialiseAdapter() throws IOException;
|
||||
|
||||
abstract boolean isAdapterEnabled();
|
||||
|
||||
/**
|
||||
* Returns the local Bluetooth address, or null if no valid address can
|
||||
* be found.
|
||||
*/
|
||||
@Nullable
|
||||
abstract String getBluetoothAddress();
|
||||
|
||||
abstract SS openServerSocket(String uuid) throws IOException;
|
||||
|
||||
abstract void tryToClose(@Nullable SS ss);
|
||||
|
||||
abstract DuplexTransportConnection acceptConnection(SS ss)
|
||||
throws IOException;
|
||||
|
||||
abstract boolean isValidAddress(String address);
|
||||
|
||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException;
|
||||
|
||||
@Nullable
|
||||
abstract DuplexTransportConnection discoverAndConnect(String uuid);
|
||||
|
||||
AbstractBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||
BluetoothConnectionFactory<S> connectionFactory,
|
||||
Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
SecureRandom secureRandom,
|
||||
Backoff backoff,
|
||||
PluginCallback callback,
|
||||
int maxLatency,
|
||||
int maxIdleTime) {
|
||||
this.connectionLimiter = connectionLimiter;
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
this.callback = callback;
|
||||
this.maxLatency = maxLatency;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
}
|
||||
|
||||
void onAdapterEnabled() {
|
||||
LOG.info("Bluetooth enabled");
|
||||
// We may not have been able to get the local address before
|
||||
ioExecutor.execute(this::updateProperties);
|
||||
if (getState() == INACTIVE) bind();
|
||||
}
|
||||
|
||||
void onAdapterDisabled() {
|
||||
LOG.info("Bluetooth disabled");
|
||||
connectionLimiter.allConnectionsClosed();
|
||||
// The server socket may not have been closed automatically
|
||||
SS ss = state.clearServerSocket();
|
||||
if (ss != null) {
|
||||
LOG.info("Closing server socket");
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return maxIdleTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
Settings settings = callback.getSettings();
|
||||
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
|
||||
DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
everConnected.set(settings.getBoolean(PREF_EVER_CONNECTED,
|
||||
DEFAULT_PREF_EVER_CONNECTED));
|
||||
state.setStarted(enabledByUser);
|
||||
try {
|
||||
initialiseAdapter();
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
updateProperties();
|
||||
if (enabledByUser && isAdapterEnabled()) bind();
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (getState() != INACTIVE) return;
|
||||
// Bind a server socket to accept connections from contacts
|
||||
SS ss;
|
||||
try {
|
||||
ss = openServerSocket(contactConnectionsUuid);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return;
|
||||
}
|
||||
if (!state.setServerSocket(ss)) {
|
||||
LOG.info("Closing redundant server socket");
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
backoff.reset();
|
||||
acceptContactConnections(ss);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateProperties() {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
String uuid = p.get(PROP_UUID);
|
||||
Settings s = callback.getSettings();
|
||||
boolean isReflected = s.getBoolean(PREF_ADDRESS_IS_REFLECTED,
|
||||
DEFAULT_PREF_ADDRESS_IS_REFLECTED);
|
||||
boolean changed = false;
|
||||
if (address == null || isReflected) {
|
||||
address = getBluetoothAddress();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
}
|
||||
if (address == null) {
|
||||
if (everConnected.get()) {
|
||||
address = getReflectedAddress();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Reflected address " +
|
||||
scrubMacAddress(address));
|
||||
}
|
||||
if (address != null) {
|
||||
changed = true;
|
||||
isReflected = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
changed = true;
|
||||
isReflected = false;
|
||||
}
|
||||
}
|
||||
if (uuid == null) {
|
||||
byte[] random = new byte[UUID_BYTES];
|
||||
secureRandom.nextBytes(random);
|
||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||
changed = true;
|
||||
}
|
||||
contactConnectionsUuid = uuid;
|
||||
if (changed) {
|
||||
p = new TransportProperties();
|
||||
// If we previously used a reflected address and there's no longer
|
||||
// a reflected address with enough votes to be used, we'll continue
|
||||
// to use the old reflected address until there's a new winner
|
||||
if (address != null) p.put(PROP_ADDRESS, address);
|
||||
p.put(PROP_UUID, uuid);
|
||||
callback.mergeLocalProperties(p);
|
||||
s = new Settings();
|
||||
s.putBoolean(PREF_ADDRESS_IS_REFLECTED, isReflected);
|
||||
callback.mergeSettings(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getReflectedAddress() {
|
||||
// Count the number of votes for each reflected address
|
||||
String key = REFLECTED_PROPERTY_PREFIX + PROP_ADDRESS;
|
||||
Multiset<String> votes = new Multiset<>();
|
||||
for (TransportProperties p : callback.getRemoteProperties()) {
|
||||
String address = p.get(key);
|
||||
if (address != null && isValidAddress(address)) votes.add(address);
|
||||
}
|
||||
// If an address gets more than half of the votes, accept it
|
||||
int total = votes.getTotal();
|
||||
for (String address : votes.keySet()) {
|
||||
if (votes.getCount(address) * 2 > total) return address;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void acceptContactConnections(SS ss) {
|
||||
while (true) {
|
||||
DuplexTransportConnection conn;
|
||||
try {
|
||||
conn = acceptConnection(ss);
|
||||
} catch (IOException e) {
|
||||
// This is expected when the server socket is closed
|
||||
LOG.info("Server socket closed");
|
||||
state.clearServerSocket();
|
||||
return;
|
||||
}
|
||||
LOG.info("Connection received");
|
||||
connectionLimiter.connectionOpened(conn);
|
||||
backoff.reset();
|
||||
setEverConnected();
|
||||
callback.handleConnection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEverConnected() {
|
||||
if (!everConnected.getAndSet(true)) {
|
||||
ioExecutor.execute(() -> {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_EVER_CONNECTED, true);
|
||||
callback.mergeSettings(s);
|
||||
// Contacts may already have sent a reflected address
|
||||
updateProperties();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
SS ss = state.setStopped();
|
||||
tryToClose(ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return state.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReasonsDisabled() {
|
||||
return state.getReasonsDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
|
||||
properties) {
|
||||
if (getState() != ACTIVE) return;
|
||||
backoff.increment();
|
||||
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
|
||||
connect(p.getFirst(), p.getSecond());
|
||||
}
|
||||
}
|
||||
|
||||
private void connect(TransportProperties p, ConnectionHandler h) {
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (isNullOrEmpty(address)) return;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (isNullOrEmpty(uuid)) return;
|
||||
wakefulIoExecutor.execute(() -> {
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
setEverConnected();
|
||||
h.handleConnection(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DuplexTransportConnection connect(String address, String uuid) {
|
||||
// Validate the address
|
||||
if (!isValidAddress(address)) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
// Not scrubbing here to be able to figure out the problem
|
||||
LOG.warning("Invalid address " + address);
|
||||
return null;
|
||||
}
|
||||
// Validate the UUID
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
UUID.fromString(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
||||
return null;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||
try {
|
||||
DuplexTransportConnection conn = connectTo(address, uuid);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connected to " + scrubMacAddress(address));
|
||||
return conn;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Could not connect to " + scrubMacAddress(address));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
if (getState() != ACTIVE) return null;
|
||||
if (!connectionLimiter.canOpenContactConnection()) return null;
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (isNullOrEmpty(uuid)) return null;
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) connectionLimiter.connectionOpened(conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (getState() != ACTIVE) return null;
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
SS ss;
|
||||
try {
|
||||
ss = openServerSocket(uuid);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
}
|
||||
if (getState() != ACTIVE) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
String address = getBluetoothAddress();
|
||||
if (address != null) descriptor.add(macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
if (getState() != ACTIVE) return null;
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
DuplexTransportConnection conn;
|
||||
if (descriptor.size() == 1) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Discovering address for key agreement UUID " +
|
||||
uuid);
|
||||
}
|
||||
conn = discoverAndConnect(uuid);
|
||||
} else {
|
||||
String address;
|
||||
try {
|
||||
address = parseAddress(descriptor);
|
||||
} catch (FormatException e) {
|
||||
LOG.info("Invalid address in key agreement descriptor");
|
||||
return null;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
conn = connect(address, uuid);
|
||||
}
|
||||
if (conn != null) {
|
||||
connectionLimiter.connectionOpened(conn);
|
||||
setEverConnected();
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
byte[] mac = descriptor.getRaw(1);
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
return macToString(mac);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDiscovering() {
|
||||
return discoverSemaphore.availablePermits() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection discoverAndConnectForSetup(String uuid) {
|
||||
DuplexTransportConnection conn = discoverAndConnect(uuid);
|
||||
if (conn != null) {
|
||||
connectionLimiter.connectionOpened(conn);
|
||||
setEverConnected();
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
|
||||
boolean alice, ConnectionHandler incoming) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString()))
|
||||
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
|
||||
} else if (e instanceof KeyAgreementListeningEvent) {
|
||||
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
|
||||
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
|
||||
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
|
||||
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
|
||||
RemoteTransportPropertiesUpdatedEvent r =
|
||||
(RemoteTransportPropertiesUpdatedEvent) e;
|
||||
if (r.getTransportId().equals(ID)) {
|
||||
ioExecutor.execute(this::updateProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void onSettingsUpdated(Settings settings) {
|
||||
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
|
||||
DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
SS ss = state.setEnabledByUser(enabledByUser);
|
||||
State s = getState();
|
||||
if (ss != null) {
|
||||
LOG.info("Disabled by user, closing server socket");
|
||||
tryToClose(ss);
|
||||
} else if (s == INACTIVE) {
|
||||
if (isAdapterEnabled()) {
|
||||
LOG.info("Enabled by user, opening server socket");
|
||||
bind();
|
||||
} else {
|
||||
LOG.info("Enabled by user but adapter is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final SS ss;
|
||||
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
|
||||
super(descriptor);
|
||||
this.ss = ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
DuplexTransportConnection conn = acceptConnection(ss);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
connectionLimiter.connectionOpened(conn);
|
||||
return new KeyAgreementConnection(conn, ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
private class PluginState {
|
||||
|
||||
@GuardedBy("this")
|
||||
private boolean started = false,
|
||||
stopped = false,
|
||||
enabledByUser = false;
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private SS serverSocket = null;
|
||||
|
||||
private synchronized void setStarted(boolean enabledByUser) {
|
||||
started = true;
|
||||
this.enabledByUser = enabledByUser;
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private synchronized SS setStopped() {
|
||||
stopped = true;
|
||||
SS ss = serverSocket;
|
||||
serverSocket = null;
|
||||
callback.pluginStateChanged(getState());
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private synchronized SS setEnabledByUser(boolean enabledByUser) {
|
||||
this.enabledByUser = enabledByUser;
|
||||
SS ss = null;
|
||||
if (!enabledByUser) {
|
||||
ss = serverSocket;
|
||||
serverSocket = null;
|
||||
}
|
||||
callback.pluginStateChanged(getState());
|
||||
return ss;
|
||||
}
|
||||
|
||||
private synchronized boolean setServerSocket(SS ss) {
|
||||
if (stopped || serverSocket != null) return false;
|
||||
serverSocket = ss;
|
||||
callback.pluginStateChanged(getState());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private synchronized SS clearServerSocket() {
|
||||
SS ss = serverSocket;
|
||||
serverSocket = null;
|
||||
callback.pluginStateChanged(getState());
|
||||
return ss;
|
||||
}
|
||||
|
||||
private synchronized State getState() {
|
||||
if (!started || stopped) return STARTING_STOPPING;
|
||||
if (!enabledByUser) return DISABLED;
|
||||
return serverSocket == null ? INACTIVE : ACTIVE;
|
||||
}
|
||||
|
||||
private synchronized int getReasonsDisabled() {
|
||||
return getState() == DISABLED ? REASON_USER : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,606 +1,18 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Multiset;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionHandler;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.event.RemoteTransportPropertiesUpdatedEvent;
|
||||
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
|
||||
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_ADDRESS_IS_REFLECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_EVER_CONNECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_ADDRESS_IS_REFLECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_EVER_CONNECTED;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.bramble.util.StringUtils.macToBytes;
|
||||
import static org.briarproject.bramble.util.StringUtils.macToString;
|
||||
@NotNullByDefault
|
||||
public interface BluetoothPlugin extends DuplexPlugin {
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
final BluetoothConnectionLimiter connectionLimiter;
|
||||
final BluetoothConnectionFactory<S> connectionFactory;
|
||||
|
||||
private final Executor ioExecutor, wakefulIoExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
private final PluginCallback callback;
|
||||
private final int maxLatency, maxIdleTime;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
private final AtomicBoolean everConnected = new AtomicBoolean(false);
|
||||
|
||||
protected final PluginState state = new PluginState();
|
||||
|
||||
private volatile String contactConnectionsUuid = null;
|
||||
|
||||
abstract void initialiseAdapter() throws IOException;
|
||||
|
||||
abstract boolean isAdapterEnabled();
|
||||
|
||||
/**
|
||||
* Returns the local Bluetooth address, or null if no valid address can
|
||||
* be found.
|
||||
*/
|
||||
@Nullable
|
||||
abstract String getBluetoothAddress();
|
||||
|
||||
abstract SS openServerSocket(String uuid) throws IOException;
|
||||
|
||||
abstract void tryToClose(@Nullable SS ss);
|
||||
|
||||
abstract DuplexTransportConnection acceptConnection(SS ss)
|
||||
throws IOException;
|
||||
|
||||
abstract boolean isValidAddress(String address);
|
||||
|
||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException;
|
||||
boolean isDiscovering();
|
||||
|
||||
@Nullable
|
||||
abstract DuplexTransportConnection discoverAndConnect(String uuid);
|
||||
DuplexTransportConnection discoverAndConnectForSetup(String uuid);
|
||||
|
||||
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||
BluetoothConnectionFactory<S> connectionFactory,
|
||||
Executor ioExecutor,
|
||||
Executor wakefulIoExecutor,
|
||||
SecureRandom secureRandom,
|
||||
Backoff backoff,
|
||||
PluginCallback callback,
|
||||
int maxLatency,
|
||||
int maxIdleTime) {
|
||||
this.connectionLimiter = connectionLimiter;
|
||||
this.connectionFactory = connectionFactory;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.wakefulIoExecutor = wakefulIoExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
this.callback = callback;
|
||||
this.maxLatency = maxLatency;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
}
|
||||
|
||||
void onAdapterEnabled() {
|
||||
LOG.info("Bluetooth enabled");
|
||||
// We may not have been able to get the local address before
|
||||
ioExecutor.execute(this::updateProperties);
|
||||
if (getState() == INACTIVE) bind();
|
||||
}
|
||||
|
||||
void onAdapterDisabled() {
|
||||
LOG.info("Bluetooth disabled");
|
||||
connectionLimiter.allConnectionsClosed();
|
||||
// The server socket may not have been closed automatically
|
||||
SS ss = state.clearServerSocket();
|
||||
if (ss != null) {
|
||||
LOG.info("Closing server socket");
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return maxIdleTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
Settings settings = callback.getSettings();
|
||||
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
|
||||
DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
everConnected.set(settings.getBoolean(PREF_EVER_CONNECTED,
|
||||
DEFAULT_PREF_EVER_CONNECTED));
|
||||
state.setStarted(enabledByUser);
|
||||
try {
|
||||
initialiseAdapter();
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
updateProperties();
|
||||
if (enabledByUser && isAdapterEnabled()) bind();
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (getState() != INACTIVE) return;
|
||||
// Bind a server socket to accept connections from contacts
|
||||
SS ss;
|
||||
try {
|
||||
ss = openServerSocket(contactConnectionsUuid);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return;
|
||||
}
|
||||
if (!state.setServerSocket(ss)) {
|
||||
LOG.info("Closing redundant server socket");
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
backoff.reset();
|
||||
acceptContactConnections(ss);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateProperties() {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
String uuid = p.get(PROP_UUID);
|
||||
Settings s = callback.getSettings();
|
||||
boolean isReflected = s.getBoolean(PREF_ADDRESS_IS_REFLECTED,
|
||||
DEFAULT_PREF_ADDRESS_IS_REFLECTED);
|
||||
boolean changed = false;
|
||||
if (address == null || isReflected) {
|
||||
address = getBluetoothAddress();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
}
|
||||
if (address == null) {
|
||||
if (everConnected.get()) {
|
||||
address = getReflectedAddress();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Reflected address " +
|
||||
scrubMacAddress(address));
|
||||
}
|
||||
if (address != null) {
|
||||
changed = true;
|
||||
isReflected = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
changed = true;
|
||||
isReflected = false;
|
||||
}
|
||||
}
|
||||
if (uuid == null) {
|
||||
byte[] random = new byte[UUID_BYTES];
|
||||
secureRandom.nextBytes(random);
|
||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||
changed = true;
|
||||
}
|
||||
contactConnectionsUuid = uuid;
|
||||
if (changed) {
|
||||
p = new TransportProperties();
|
||||
// If we previously used a reflected address and there's no longer
|
||||
// a reflected address with enough votes to be used, we'll continue
|
||||
// to use the old reflected address until there's a new winner
|
||||
if (address != null) p.put(PROP_ADDRESS, address);
|
||||
p.put(PROP_UUID, uuid);
|
||||
callback.mergeLocalProperties(p);
|
||||
s = new Settings();
|
||||
s.putBoolean(PREF_ADDRESS_IS_REFLECTED, isReflected);
|
||||
callback.mergeSettings(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getReflectedAddress() {
|
||||
// Count the number of votes for each reflected address
|
||||
String key = REFLECTED_PROPERTY_PREFIX + PROP_ADDRESS;
|
||||
Multiset<String> votes = new Multiset<>();
|
||||
for (TransportProperties p : callback.getRemoteProperties()) {
|
||||
String address = p.get(key);
|
||||
if (address != null && isValidAddress(address)) votes.add(address);
|
||||
}
|
||||
// If an address gets more than half of the votes, accept it
|
||||
int total = votes.getTotal();
|
||||
for (String address : votes.keySet()) {
|
||||
if (votes.getCount(address) * 2 > total) return address;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void acceptContactConnections(SS ss) {
|
||||
while (true) {
|
||||
DuplexTransportConnection conn;
|
||||
try {
|
||||
conn = acceptConnection(ss);
|
||||
} catch (IOException e) {
|
||||
// This is expected when the server socket is closed
|
||||
LOG.info("Server socket closed");
|
||||
state.clearServerSocket();
|
||||
return;
|
||||
}
|
||||
LOG.info("Connection received");
|
||||
connectionLimiter.connectionOpened(conn);
|
||||
backoff.reset();
|
||||
setEverConnected();
|
||||
callback.handleConnection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEverConnected() {
|
||||
if (!everConnected.getAndSet(true)) {
|
||||
ioExecutor.execute(() -> {
|
||||
Settings s = new Settings();
|
||||
s.putBoolean(PREF_EVER_CONNECTED, true);
|
||||
callback.mergeSettings(s);
|
||||
// Contacts may already have sent a reflected address
|
||||
updateProperties();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
SS ss = state.setStopped();
|
||||
tryToClose(ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return state.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReasonsDisabled() {
|
||||
return state.getReasonsDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
|
||||
properties) {
|
||||
if (getState() != ACTIVE) return;
|
||||
backoff.increment();
|
||||
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
|
||||
connect(p.getFirst(), p.getSecond());
|
||||
}
|
||||
}
|
||||
|
||||
private void connect(TransportProperties p, ConnectionHandler h) {
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (isNullOrEmpty(address)) return;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (isNullOrEmpty(uuid)) return;
|
||||
wakefulIoExecutor.execute(() -> {
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
setEverConnected();
|
||||
h.handleConnection(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DuplexTransportConnection connect(String address, String uuid) {
|
||||
// Validate the address
|
||||
if (!isValidAddress(address)) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
// Not scrubbing here to be able to figure out the problem
|
||||
LOG.warning("Invalid address " + address);
|
||||
return null;
|
||||
}
|
||||
// Validate the UUID
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
UUID.fromString(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
||||
return null;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||
try {
|
||||
DuplexTransportConnection conn = connectTo(address, uuid);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connected to " + scrubMacAddress(address));
|
||||
return conn;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Could not connect to " + scrubMacAddress(address));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
if (getState() != ACTIVE) return null;
|
||||
if (!connectionLimiter.canOpenContactConnection()) return null;
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (isNullOrEmpty(uuid)) return null;
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) connectionLimiter.connectionOpened(conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (getState() != ACTIVE) return null;
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
SS ss;
|
||||
try {
|
||||
ss = openServerSocket(uuid);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
}
|
||||
if (getState() != ACTIVE) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
String address = getBluetoothAddress();
|
||||
if (address != null) descriptor.add(macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
if (getState() != ACTIVE) return null;
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
DuplexTransportConnection conn;
|
||||
if (descriptor.size() == 1) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Discovering address for key agreement UUID " +
|
||||
uuid);
|
||||
}
|
||||
conn = discoverAndConnect(uuid);
|
||||
} else {
|
||||
String address;
|
||||
try {
|
||||
address = parseAddress(descriptor);
|
||||
} catch (FormatException e) {
|
||||
LOG.info("Invalid address in key agreement descriptor");
|
||||
return null;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
conn = connect(address, uuid);
|
||||
}
|
||||
if (conn != null) {
|
||||
connectionLimiter.connectionOpened(conn);
|
||||
setEverConnected();
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
byte[] mac = descriptor.getRaw(1);
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
return macToString(mac);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
|
||||
boolean alice, ConnectionHandler incoming) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString()))
|
||||
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
|
||||
} else if (e instanceof KeyAgreementListeningEvent) {
|
||||
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
|
||||
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
|
||||
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
|
||||
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
|
||||
RemoteTransportPropertiesUpdatedEvent r =
|
||||
(RemoteTransportPropertiesUpdatedEvent) e;
|
||||
if (r.getTransportId().equals(ID)) {
|
||||
ioExecutor.execute(this::updateProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void onSettingsUpdated(Settings settings) {
|
||||
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
|
||||
DEFAULT_PREF_PLUGIN_ENABLE);
|
||||
SS ss = state.setEnabledByUser(enabledByUser);
|
||||
State s = getState();
|
||||
if (ss != null) {
|
||||
LOG.info("Disabled by user, closing server socket");
|
||||
tryToClose(ss);
|
||||
} else if (s == INACTIVE) {
|
||||
if (isAdapterEnabled()) {
|
||||
LOG.info("Enabled by user, opening server socket");
|
||||
bind();
|
||||
} else {
|
||||
LOG.info("Enabled by user but adapter is disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final SS ss;
|
||||
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
|
||||
super(descriptor);
|
||||
this.ss = ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
DuplexTransportConnection conn = acceptConnection(ss);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
connectionLimiter.connectionOpened(conn);
|
||||
return new KeyAgreementConnection(conn, ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
protected class PluginState {
|
||||
|
||||
@GuardedBy("this")
|
||||
private boolean started = false,
|
||||
stopped = false,
|
||||
enabledByUser = false;
|
||||
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private SS serverSocket = null;
|
||||
|
||||
synchronized void setStarted(boolean enabledByUser) {
|
||||
started = true;
|
||||
this.enabledByUser = enabledByUser;
|
||||
callback.pluginStateChanged(getState());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized SS setStopped() {
|
||||
stopped = true;
|
||||
SS ss = serverSocket;
|
||||
serverSocket = null;
|
||||
callback.pluginStateChanged(getState());
|
||||
return ss;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized SS setEnabledByUser(boolean enabledByUser) {
|
||||
this.enabledByUser = enabledByUser;
|
||||
SS ss = null;
|
||||
if (!enabledByUser) {
|
||||
ss = serverSocket;
|
||||
serverSocket = null;
|
||||
}
|
||||
callback.pluginStateChanged(getState());
|
||||
return ss;
|
||||
}
|
||||
|
||||
synchronized boolean setServerSocket(SS ss) {
|
||||
if (stopped || serverSocket != null) return false;
|
||||
serverSocket = ss;
|
||||
callback.pluginStateChanged(getState());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized SS clearServerSocket() {
|
||||
SS ss = serverSocket;
|
||||
serverSocket = null;
|
||||
callback.pluginStateChanged(getState());
|
||||
return ss;
|
||||
}
|
||||
|
||||
synchronized State getState() {
|
||||
if (!started || stopped) return STARTING_STOPPING;
|
||||
if (!enabledByUser) return DISABLED;
|
||||
return serverSocket == null ? INACTIVE : ACTIVE;
|
||||
}
|
||||
|
||||
synchronized int getReasonsDisabled() {
|
||||
return getState() == DISABLED ? REASON_USER : 0;
|
||||
}
|
||||
}
|
||||
void stopDiscoverAndConnect();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.ConnectionHandler;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(AbstractRemovableDrivePlugin.class.getName());
|
||||
|
||||
private final int maxLatency;
|
||||
|
||||
abstract InputStream openInputStream(TransportProperties p)
|
||||
throws IOException;
|
||||
|
||||
abstract OutputStream openOutputStream(TransportProperties p)
|
||||
throws IOException;
|
||||
|
||||
AbstractRemovableDrivePlugin(int maxLatency) {
|
||||
this.maxLatency = maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
// Unused for simplex transports
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return ACTIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReasonsDisabled() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(
|
||||
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportConnectionReader createReader(TransportProperties p) {
|
||||
try {
|
||||
return new TransportInputStreamReader(openInputStream(p));
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportConnectionWriter createWriter(TransportProperties p) {
|
||||
try {
|
||||
return new TransportOutputStreamWriter(this, openOutputStream(p));
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class RemovableDriveManagerImpl
|
||||
implements RemovableDriveManager, RemovableDriveTaskRegistry {
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final RemovableDriveTaskFactory taskFactory;
|
||||
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
|
||||
readers = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<ContactId, RemovableDriveTask>
|
||||
writers = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
|
||||
RemovableDriveTaskFactory taskFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.taskFactory = taskFactory;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RemovableDriveTask getCurrentReaderTask(ContactId c) {
|
||||
return readers.get(c);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RemovableDriveTask getCurrentWriterTask(ContactId c) {
|
||||
return writers.get(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask startReaderTask(ContactId c,
|
||||
TransportProperties p) {
|
||||
RemovableDriveTask task = taskFactory.createReader(this, c, p);
|
||||
RemovableDriveTask old = readers.putIfAbsent(c, task);
|
||||
if (old == null) {
|
||||
ioExecutor.execute(task);
|
||||
return task;
|
||||
} else {
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask startWriterTask(ContactId c,
|
||||
TransportProperties p) {
|
||||
RemovableDriveTask task = taskFactory.createWriter(this, c, p);
|
||||
RemovableDriveTask old = writers.putIfAbsent(c, task);
|
||||
if (old == null) {
|
||||
ioExecutor.execute(task);
|
||||
return task;
|
||||
} else {
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeReader(ContactId c, RemovableDriveTask task) {
|
||||
readers.remove(c, task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWriter(ContactId c, RemovableDriveTask task) {
|
||||
writers.remove(c, task);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class RemovableDriveModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
RemovableDriveManager provideRemovableDriveManager(
|
||||
RemovableDriveManagerImpl removableDriveManager) {
|
||||
return removableDriveManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
RemovableDriveTaskFactory provideTaskFactory(
|
||||
RemovableDriveTaskFactoryImpl taskFactory) {
|
||||
return taskFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class RemovableDrivePlugin extends AbstractRemovableDrivePlugin {
|
||||
|
||||
RemovableDrivePlugin(int maxLatency) {
|
||||
super(maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream openInputStream(TransportProperties p) throws IOException {
|
||||
String path = p.get(PROP_PATH);
|
||||
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
|
||||
return new FileInputStream(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
OutputStream openOutputStream(TransportProperties p) throws IOException {
|
||||
String path = p.get(PROP_PATH);
|
||||
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
|
||||
return new FileOutputStream(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.DAYS;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
||||
|
||||
@Inject
|
||||
RemovableDrivePluginFactory() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return MAX_LATENCY;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SimplexPlugin createPlugin(PluginCallback callback) {
|
||||
return new RemovableDrivePlugin(MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@NotNullByDefault
|
||||
class RemovableDriveReaderTask extends RemovableDriveTaskImpl
|
||||
implements EventListener {
|
||||
|
||||
private final static Logger LOG =
|
||||
getLogger(RemovableDriveReaderTask.class.getName());
|
||||
|
||||
RemovableDriveReaderTask(
|
||||
Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus,
|
||||
RemovableDriveTaskRegistry registry,
|
||||
ContactId contactId,
|
||||
TransportProperties transportProperties) {
|
||||
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||
registry, contactId, transportProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
TransportConnectionReader r =
|
||||
getPlugin().createReader(transportProperties);
|
||||
if (r == null) {
|
||||
LOG.warning("Failed to create reader");
|
||||
registry.removeReader(contactId, this);
|
||||
setSuccess(false);
|
||||
return;
|
||||
}
|
||||
eventBus.addListener(this);
|
||||
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageAddedEvent) {
|
||||
MessageAddedEvent m = (MessageAddedEvent) e;
|
||||
if (contactId.equals(m.getContactId())) {
|
||||
LOG.info("Message received");
|
||||
addDone(m.getMessage().getRawLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DecoratedReader implements TransportConnectionReader {
|
||||
|
||||
private final TransportConnectionReader delegate;
|
||||
|
||||
private DecoratedReader(TransportConnectionReader delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return delegate.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception, boolean recognised)
|
||||
throws IOException {
|
||||
delegate.dispose(exception, recognised);
|
||||
registry.removeReader(contactId, RemovableDriveReaderTask.this);
|
||||
eventBus.removeListener(RemovableDriveReaderTask.this);
|
||||
setSuccess(!exception && recognised);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
@NotNullByDefault
|
||||
interface RemovableDriveTaskFactory {
|
||||
|
||||
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p);
|
||||
|
||||
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor eventExecutor;
|
||||
private final PluginManager pluginManager;
|
||||
private final ConnectionManager connectionManager;
|
||||
private final EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
RemovableDriveTaskFactoryImpl(
|
||||
DatabaseComponent db,
|
||||
@EventExecutor Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus) {
|
||||
this.db = db;
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionManager = connectionManager;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p) {
|
||||
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
|
||||
connectionManager, eventBus, registry, c, p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
|
||||
ContactId c, TransportProperties p) {
|
||||
return new RemovableDriveWriterTask(db, eventExecutor, pluginManager,
|
||||
connectionManager, eventBus, registry, c, p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.Consumer;
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
|
||||
|
||||
private final Executor eventExecutor;
|
||||
private final PluginManager pluginManager;
|
||||
final ConnectionManager connectionManager;
|
||||
final EventBus eventBus;
|
||||
final RemovableDriveTaskRegistry registry;
|
||||
final ContactId contactId;
|
||||
final TransportProperties transportProperties;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
private final List<Consumer<State>> observers = new ArrayList<>();
|
||||
@GuardedBy("lock")
|
||||
private State state = new State(0, 0, false, false);
|
||||
|
||||
RemovableDriveTaskImpl(
|
||||
Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus,
|
||||
RemovableDriveTaskRegistry registry,
|
||||
ContactId contactId,
|
||||
TransportProperties transportProperties) {
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionManager = connectionManager;
|
||||
this.eventBus = eventBus;
|
||||
this.registry = registry;
|
||||
this.contactId = contactId;
|
||||
this.transportProperties = transportProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties getTransportProperties() {
|
||||
return transportProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addObserver(Consumer<State> o) {
|
||||
State state;
|
||||
synchronized (lock) {
|
||||
observers.add(o);
|
||||
state = this.state;
|
||||
}
|
||||
if (state.isFinished()) {
|
||||
eventExecutor.execute(() -> o.accept(state));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObserver(Consumer<State> o) {
|
||||
synchronized (lock) {
|
||||
observers.remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
SimplexPlugin getPlugin() {
|
||||
return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
|
||||
}
|
||||
|
||||
void setTotal(long total) {
|
||||
synchronized (lock) {
|
||||
state = new State(state.getDone(), total, state.isFinished(),
|
||||
state.isSuccess());
|
||||
notifyObservers();
|
||||
}
|
||||
}
|
||||
|
||||
void addDone(long done) {
|
||||
synchronized (lock) {
|
||||
// Done and total come from different sources; make them consistent
|
||||
done = min(state.getDone() + done, state.getTotal());
|
||||
state = new State(done, state.getTotal(), state.isFinished(),
|
||||
state.isSuccess());
|
||||
}
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
void setSuccess(boolean success) {
|
||||
synchronized (lock) {
|
||||
state = new State(state.getDone(), state.getTotal(), true, success);
|
||||
}
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void notifyObservers() {
|
||||
List<Consumer<State>> observers = new ArrayList<>(this.observers);
|
||||
State state = this.state;
|
||||
eventExecutor.execute(() -> {
|
||||
for (Consumer<State> o : observers) o.accept(state);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
|
||||
@NotNullByDefault
|
||||
interface RemovableDriveTaskRegistry {
|
||||
|
||||
void removeReader(ContactId c, RemovableDriveTask task);
|
||||
|
||||
void removeWriter(ContactId c, RemovableDriveTask task);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
class RemovableDriveWriterTask extends RemovableDriveTaskImpl
|
||||
implements EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(RemovableDriveWriterTask.class.getName());
|
||||
|
||||
private final DatabaseComponent db;
|
||||
|
||||
RemovableDriveWriterTask(
|
||||
DatabaseComponent db,
|
||||
Executor eventExecutor,
|
||||
PluginManager pluginManager,
|
||||
ConnectionManager connectionManager,
|
||||
EventBus eventBus,
|
||||
RemovableDriveTaskRegistry registry,
|
||||
ContactId contactId,
|
||||
TransportProperties transportProperties) {
|
||||
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||
registry, contactId, transportProperties);
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
SimplexPlugin plugin = getPlugin();
|
||||
TransportConnectionWriter w = plugin.createWriter(transportProperties);
|
||||
if (w == null) {
|
||||
LOG.warning("Failed to create writer");
|
||||
registry.removeWriter(contactId, this);
|
||||
setSuccess(false);
|
||||
return;
|
||||
}
|
||||
int maxLatency = plugin.getMaxLatency();
|
||||
try {
|
||||
setTotal(db.transactionWithResult(true, txn ->
|
||||
db.getMessageBytesToSend(txn, contactId, maxLatency)));
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
registry.removeWriter(contactId, this);
|
||||
setSuccess(false);
|
||||
return;
|
||||
}
|
||||
eventBus.addListener(this);
|
||||
connectionManager.manageOutgoingConnection(contactId, ID,
|
||||
new DecoratedWriter(w));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessagesSentEvent) {
|
||||
MessagesSentEvent m = (MessagesSentEvent) e;
|
||||
if (contactId.equals(m.getContactId())) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(m.getMessageIds().size() + " messages sent");
|
||||
}
|
||||
addDone(m.getTotalLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DecoratedWriter implements TransportConnectionWriter {
|
||||
|
||||
private final TransportConnectionWriter delegate;
|
||||
|
||||
private DecoratedWriter(TransportConnectionWriter delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return delegate.getMaxLatency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return delegate.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return delegate.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception) throws IOException {
|
||||
delegate.dispose(exception);
|
||||
registry.removeWriter(contactId, RemovableDriveWriterTask.this);
|
||||
eventBus.removeListener(RemovableDriveWriterTask.this);
|
||||
setSuccess(!exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class TransportInputStreamReader implements TransportConnectionReader {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(TransportInputStreamReader.class.getName());
|
||||
|
||||
private final InputStream in;
|
||||
|
||||
TransportInputStreamReader(InputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception, boolean recognised) {
|
||||
tryToClose(in, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
|
||||
@NotNullByDefault
|
||||
class TransportOutputStreamWriter implements TransportConnectionWriter {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(TransportOutputStreamWriter.class.getName());
|
||||
|
||||
private final Plugin plugin;
|
||||
private final OutputStream out;
|
||||
|
||||
TransportOutputStreamWriter(Plugin plugin, OutputStream out) {
|
||||
this.plugin = plugin;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return plugin.getMaxLatency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
return plugin.getMaxIdleTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(boolean exception) {
|
||||
tryToClose(out, LOG, WARNING);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,5 @@ interface ClientVersioningConstants {
|
||||
// Metadata keys
|
||||
String MSG_KEY_UPDATE_VERSION = "version";
|
||||
String MSG_KEY_LOCAL = "local";
|
||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ import static java.util.Collections.emptyList;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||
|
||||
@@ -161,13 +160,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
db.addGroup(txn, g);
|
||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||
// Attach the contact ID to the group
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
||||
try {
|
||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
||||
} catch (FormatException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||
// Create and store the first local update
|
||||
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||
Collections.sort(versions);
|
||||
@@ -229,7 +222,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
Map<ClientMajorVersion, Visibility> after =
|
||||
getVisibilities(newLocalStates, newRemoteStates);
|
||||
// Call hooks for any visibilities that have changed
|
||||
ContactId c = getContactId(txn, m.getGroupId());
|
||||
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||
if (!before.equals(after)) {
|
||||
Contact contact = db.getContact(txn, c);
|
||||
callVisibilityHooks(txn, contact, before, after);
|
||||
@@ -521,17 +514,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
||||
storeUpdate(txn, g, states, 1);
|
||||
}
|
||||
|
||||
private ContactId getContactId(Transaction txn, GroupId g)
|
||||
throws DbException {
|
||||
try {
|
||||
BdfDictionary meta =
|
||||
clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ClientState> updateStatesFromRemoteStates(
|
||||
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
|
||||
Set<ClientMajorVersion> remoteSet = new HashSet<>();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
||||
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
|
||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
|
||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
|
||||
import org.briarproject.bramble.system.TimeTravelModule;
|
||||
|
||||
public interface BrambleCoreIntegrationTestEagerSingletons
|
||||
extends BrambleCoreEagerSingletons {
|
||||
|
||||
void inject(DefaultTaskSchedulerModule.EagerSingletons init);
|
||||
void inject(TimeTravelModule.EagerSingletons init);
|
||||
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(
|
||||
BrambleCoreIntegrationTestEagerSingletons c) {
|
||||
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
|
||||
c.inject(new DefaultTaskSchedulerModule.EagerSingletons());
|
||||
c.inject(new TimeTravelModule.EagerSingletons());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
@@ -69,6 +70,8 @@ import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
@@ -295,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(18).of(database).startTransaction();
|
||||
exactly(19).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(18).of(database).containsContact(txn, contactId);
|
||||
exactly(19).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(18).of(database).abortTransaction(txn);
|
||||
exactly(19).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
@@ -346,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getContact(transaction, contactId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -354,7 +357,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageBytesToSend(transaction, contactId, 123));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, groupId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -362,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, messageId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -370,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getGroupVisibility(transaction, contactId, groupId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -378,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getSyncVersions(transaction, contactId));
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -510,11 +521,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the group is in the DB (which it's not)
|
||||
exactly(8).of(database).startTransaction();
|
||||
exactly(10).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(8).of(database).containsGroup(txn, groupId);
|
||||
exactly(10).of(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
exactly(8).of(database).abortTransaction(txn);
|
||||
exactly(10).of(database).abortTransaction(txn);
|
||||
// Allow other checks to pass
|
||||
allowing(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
@@ -523,7 +534,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getGroup(transaction, groupId));
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
@@ -531,7 +542,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getGroupMetadata(transaction, groupId));
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
@@ -539,7 +550,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageIds(transaction, groupId));
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageIds(transaction, groupId, new Metadata()));
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageMetadata(transaction, groupId));
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
@@ -547,7 +574,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageMetadata(transaction, groupId,
|
||||
new Metadata()));
|
||||
fail();
|
||||
@@ -556,7 +583,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, groupId));
|
||||
fail();
|
||||
} catch (NoSuchGroupException expected) {
|
||||
@@ -594,11 +621,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the message is in the DB (which it's not)
|
||||
exactly(12).of(database).startTransaction();
|
||||
exactly(15).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(12).of(database).containsMessage(txn, messageId);
|
||||
exactly(15).of(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
exactly(12).of(database).abortTransaction(txn);
|
||||
exactly(15).of(database).abortTransaction(txn);
|
||||
// Allow other checks to pass
|
||||
allowing(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
@@ -623,7 +650,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessage(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
@@ -631,7 +658,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageMetadata(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
@@ -639,7 +666,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageState(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
@@ -647,7 +674,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageStatus(transaction, contactId, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
@@ -662,6 +689,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.setCleanupTimerDuration(transaction, message.getId(),
|
||||
HOURS.toMillis(1)));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.setMessagePermanent(transaction, message.getId()));
|
||||
@@ -687,7 +723,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageDependencies(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
@@ -695,12 +731,28 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.transaction(true, transaction ->
|
||||
db.getMessageDependents(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.startCleanupTimer(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
try {
|
||||
db.transaction(false, transaction ->
|
||||
db.stopCleanupTimer(transaction, messageId));
|
||||
fail();
|
||||
} catch (NoSuchMessageException expected) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -981,6 +1033,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).startCleanupTimer(txn, messageId);
|
||||
will(returnValue(TIMER_NOT_STARTED)); // No cleanup duration was set
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
|
||||
}});
|
||||
@@ -993,6 +1048,56 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveDuplicateAck() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
|
||||
will(returnValue(false)); // Already acked
|
||||
oneOf(database).commitTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveAckWithCleanupTimer() throws Exception {
|
||||
long deadline = System.currentTimeMillis();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).startCleanupTimer(txn, messageId);
|
||||
will(returnValue(deadline));
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
CleanupTimerStartedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
eventExecutor, shutdownManager);
|
||||
|
||||
db.transaction(false, transaction -> {
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveMessage() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
|
||||
@@ -41,8 +41,12 @@ import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Driver;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -53,10 +57,11 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
|
||||
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
@@ -222,6 +227,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Changing the status to seen = true should make the message unsendable
|
||||
db.raiseSeenFlag(txn, contactId, messageId);
|
||||
@@ -229,6 +236,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -253,6 +261,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Marking the message delivered should make it sendable
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
@@ -260,6 +269,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Marking the message invalid should make it unsendable
|
||||
db.setMessageState(txn, messageId, INVALID);
|
||||
@@ -267,6 +278,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Marking the message pending should make it unsendable
|
||||
db.setMessageState(txn, messageId, PENDING);
|
||||
@@ -274,6 +286,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -297,6 +310,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Making the group visible should not make the message sendable
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
@@ -304,6 +318,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Sharing the group should make the message sendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
@@ -311,6 +326,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Unsharing the group should make the message unsendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
@@ -318,6 +335,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Making the group invisible should make the message unsendable
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
@@ -325,6 +343,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -349,13 +368,16 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(0, db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// Sharing the message should make it sendable
|
||||
db.setMessageShared(txn, messageId);
|
||||
db.setMessageShared(txn, messageId, true);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -379,10 +401,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
||||
MAX_LATENCY);
|
||||
assertTrue(ids.isEmpty());
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
// The message is just the right size to send
|
||||
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
||||
MAX_LATENCY);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(message.getRawLength(),
|
||||
db.getMessageBytesToSend(txn, contactId, MAX_LATENCY));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -631,8 +658,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// The group should not be visible to the contact
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(emptyMap(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
assertTrue(db.getGroupVisibility(txn, groupId).isEmpty());
|
||||
|
||||
// Make the group visible to the contact
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
@@ -655,8 +681,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Make the group invisible again
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(emptyMap(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
assertTrue(db.getGroupVisibility(txn, groupId).isEmpty());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -2040,7 +2065,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Share the message - now it should be sendable immediately
|
||||
db.setMessageShared(txn, messageId);
|
||||
db.setMessageShared(txn, messageId, true);
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Mark the message as requested - it should still be sendable
|
||||
@@ -2347,6 +2372,148 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testShutdownGracefully() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
db.close();
|
||||
open(true);
|
||||
assertFalse(db.wasDirtyOnInitialisation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdownDirty() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
|
||||
// We want to simulate a dirty shutdown here which would normally be
|
||||
// caused by an empty battery or by force closing the Android app.
|
||||
// As there is no obvious way to simulate this, we're artificially
|
||||
// causing an SqlException during close() here by unloading the JDBC
|
||||
// drivers.
|
||||
List<String> unloadedDrivers = unloadDrivers();
|
||||
|
||||
try {
|
||||
db.close();
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
// continue
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Reloading drivers to continue so that we're able to work with the
|
||||
// database again.
|
||||
reloadDrivers(unloadedDrivers);
|
||||
|
||||
db = open(true);
|
||||
assertTrue(db.wasDirtyOnInitialisation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShutdownDirtyThenGracefully() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
|
||||
// Simulating a dirty shutdown here, look at #testShutdownDirty for
|
||||
// details.
|
||||
List<String> unloadedDrivers = unloadDrivers();
|
||||
|
||||
try {
|
||||
db.close();
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
// continue
|
||||
}
|
||||
|
||||
reloadDrivers(unloadedDrivers);
|
||||
|
||||
db = open(true);
|
||||
assertTrue(db.wasDirtyOnInitialisation());
|
||||
|
||||
db.close();
|
||||
db = open(true);
|
||||
assertFalse(db.wasDirtyOnInitialisation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanupTimer() throws Exception {
|
||||
long duration = 60_000;
|
||||
long now = System.currentTimeMillis();
|
||||
AtomicLong time = new AtomicLong(now);
|
||||
Database<Connection> db =
|
||||
open(false, new TestMessageFactory(), new SettableClock(time));
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// No messages should be due or scheduled for deletion
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, false, false, null);
|
||||
|
||||
// No messages should be due or scheduled for deletion
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// Set the message's cleanup timer duration
|
||||
db.setCleanupTimerDuration(txn, messageId, duration);
|
||||
|
||||
// No messages should be due or scheduled for deletion
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// Start the message's cleanup timer
|
||||
assertEquals(now + duration, db.startCleanupTimer(txn, messageId));
|
||||
|
||||
// The timer can't be started again
|
||||
assertEquals(TIMER_NOT_STARTED, db.startCleanupTimer(txn, messageId));
|
||||
|
||||
// No messages should be due for deletion, but the message should be
|
||||
// scheduled for deletion
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// Stop the timer
|
||||
db.stopCleanupTimer(txn, messageId);
|
||||
|
||||
// No messages should be due or scheduled for deletion
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// Start the timer again
|
||||
assertEquals(now + duration, db.startCleanupTimer(txn, messageId));
|
||||
|
||||
// No messages should be due for deletion, but the message should be
|
||||
// scheduled for deletion
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// 1 ms before the timer expires, no messages should be due for
|
||||
// deletion but the message should be scheduled for deletion
|
||||
time.set(now + duration - 1);
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// When the timer expires, the message should be due and scheduled for
|
||||
// deletion
|
||||
time.set(now + duration);
|
||||
assertEquals(singletonMap(groupId, singletonList(messageId)),
|
||||
db.getMessagesToDelete(txn));
|
||||
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// 1 ms after the timer expires, the message should be due and
|
||||
// scheduled for deletion
|
||||
time.set(now + duration + 1);
|
||||
assertEquals(singletonMap(groupId, singletonList(messageId)),
|
||||
db.getMessagesToDelete(txn));
|
||||
assertEquals(now + duration, db.getNextCleanupDeadline(txn));
|
||||
|
||||
// Once the message has been deleted, it should no longer be due
|
||||
// or scheduled for deletion
|
||||
db.deleteMessage(txn, messageId);
|
||||
assertTrue(db.getMessagesToDelete(txn).isEmpty());
|
||||
assertEquals(NO_CLEANUP_DEADLINE, db.getNextCleanupDeadline(txn));
|
||||
}
|
||||
|
||||
private Database<Connection> open(boolean resume) throws Exception {
|
||||
return open(resume, new TestMessageFactory(), new SystemClock());
|
||||
}
|
||||
@@ -2402,6 +2569,31 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
rootKey, alice);
|
||||
}
|
||||
|
||||
private List<String> unloadDrivers() {
|
||||
Enumeration<Driver> drivers = DriverManager.getDrivers();
|
||||
List<String> unloaded = new ArrayList<>();
|
||||
while (drivers.hasMoreElements()) {
|
||||
Driver d = drivers.nextElement();
|
||||
try {
|
||||
DriverManager.deregisterDriver(d);
|
||||
unloaded.add(d.getClass().getName());
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
}
|
||||
return unloaded;
|
||||
}
|
||||
|
||||
private void reloadDrivers(List<String> unloadedDrivers)
|
||||
throws ClassNotFoundException, IllegalAccessException,
|
||||
InstantiationException, SQLException {
|
||||
for (String driverName : unloadedDrivers) {
|
||||
DriverManager.registerDriver(
|
||||
(Driver) Class.forName(driverName).newInstance());
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
deleteTestDirectory(testDir);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
|
||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RemovableDriveIntegrationTest extends BrambleTestCase {
|
||||
|
||||
private static final int TIMEOUT_MS = 5_000;
|
||||
|
||||
private final File testDir = getTestDirectory();
|
||||
private final File aliceDir = new File(testDir, "alice");
|
||||
private final File bobDir = new File(testDir, "bob");
|
||||
|
||||
private final SecretKey rootKey = getSecretKey();
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
|
||||
private RemovableDriveIntegrationTestComponent alice, bob;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
assertTrue(testDir.mkdirs());
|
||||
alice = DaggerRemovableDriveIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(aliceDir)).build();
|
||||
RemovableDriveIntegrationTestComponent.Helper
|
||||
.injectEagerSingletons(alice);
|
||||
bob = DaggerRemovableDriveIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(bobDir)).build();
|
||||
RemovableDriveIntegrationTestComponent.Helper
|
||||
.injectEagerSingletons(bob);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAndRead() throws Exception {
|
||||
// Create the identities
|
||||
Identity aliceIdentity =
|
||||
alice.getIdentityManager().createIdentity("Alice");
|
||||
Identity bobIdentity = bob.getIdentityManager().createIdentity("Bob");
|
||||
// Set up the devices and get the contact IDs
|
||||
ContactId bobId = setUp(alice, aliceIdentity,
|
||||
bobIdentity.getLocalAuthor(), true);
|
||||
ContactId aliceId = setUp(bob, bobIdentity,
|
||||
aliceIdentity.getLocalAuthor(), false);
|
||||
// Sync Alice's client versions and transport properties
|
||||
read(bob, aliceId, write(alice, bobId), 2);
|
||||
// Sync Bob's client versions and transport properties
|
||||
read(alice, bobId, write(bob, aliceId), 2);
|
||||
}
|
||||
|
||||
private ContactId setUp(RemovableDriveIntegrationTestComponent device,
|
||||
Identity local, Author remote, boolean alice) throws Exception {
|
||||
// Add an identity for the user
|
||||
IdentityManager identityManager = device.getIdentityManager();
|
||||
identityManager.registerIdentity(local);
|
||||
// Start the lifecycle manager
|
||||
LifecycleManager lifecycleManager = device.getLifecycleManager();
|
||||
lifecycleManager.startServices(getSecretKey());
|
||||
lifecycleManager.waitForStartup();
|
||||
// Add the other user as a contact
|
||||
ContactManager contactManager = device.getContactManager();
|
||||
return contactManager.addContact(remote, local.getId(), rootKey,
|
||||
timestamp, alice, true, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void read(RemovableDriveIntegrationTestComponent device,
|
||||
ContactId contactId, File file, int deliveries) throws Exception {
|
||||
// Listen for message deliveries
|
||||
MessageDeliveryListener listener =
|
||||
new MessageDeliveryListener(deliveries);
|
||||
device.getEventBus().addListener(listener);
|
||||
// Read the incoming stream
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_PATH, file.getAbsolutePath());
|
||||
RemovableDriveTask reader = device.getRemovableDriveManager()
|
||||
.startReaderTask(contactId, p);
|
||||
CountDownLatch disposedLatch = new CountDownLatch(1);
|
||||
reader.addObserver(state -> {
|
||||
if (state.isFinished()) disposedLatch.countDown();
|
||||
});
|
||||
// Wait for the messages to be delivered
|
||||
assertTrue(listener.delivered.await(TIMEOUT_MS, MILLISECONDS));
|
||||
// Clean up the listener
|
||||
device.getEventBus().removeListener(listener);
|
||||
// Wait for the reader to be disposed
|
||||
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
|
||||
}
|
||||
|
||||
private File write(RemovableDriveIntegrationTestComponent device,
|
||||
ContactId contactId) throws Exception {
|
||||
// Write the outgoing stream to a file
|
||||
File file = File.createTempFile("sync", ".tmp", testDir);
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_PATH, file.getAbsolutePath());
|
||||
RemovableDriveTask writer = device.getRemovableDriveManager()
|
||||
.startWriterTask(contactId, p);
|
||||
CountDownLatch disposedLatch = new CountDownLatch(1);
|
||||
writer.addObserver(state -> {
|
||||
if (state.isFinished()) disposedLatch.countDown();
|
||||
});
|
||||
// Wait for the writer to be disposed
|
||||
disposedLatch.await(TIMEOUT_MS, MILLISECONDS);
|
||||
// Return the file containing the stream
|
||||
return file;
|
||||
}
|
||||
|
||||
private void tearDown(RemovableDriveIntegrationTestComponent device)
|
||||
throws Exception {
|
||||
// Stop the lifecycle manager
|
||||
LifecycleManager lifecycleManager = device.getLifecycleManager();
|
||||
lifecycleManager.stopServices();
|
||||
lifecycleManager.waitForShutdown();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
// Tear down the devices
|
||||
tearDown(alice);
|
||||
tearDown(bob);
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private static class MessageDeliveryListener implements EventListener {
|
||||
|
||||
private final CountDownLatch delivered;
|
||||
|
||||
private MessageDeliveryListener(int deliveries) {
|
||||
delivered = new CountDownLatch(deliveries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
|
||||
if (m.getState().equals(DELIVERED)) delivered.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
|
||||
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
|
||||
import org.briarproject.bramble.event.DefaultEventExecutorModule;
|
||||
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
|
||||
import org.briarproject.bramble.system.TimeTravelModule;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.bramble.test.TestSecureRandomModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
BrambleCoreModule.class,
|
||||
DefaultBatteryManagerModule.class,
|
||||
DefaultEventExecutorModule.class,
|
||||
DefaultWakefulIoExecutorModule.class,
|
||||
TestDatabaseConfigModule.class,
|
||||
RemovableDriveIntegrationTestModule.class,
|
||||
RemovableDriveModule.class,
|
||||
TestSecureRandomModule.class,
|
||||
TimeTravelModule.class
|
||||
})
|
||||
interface RemovableDriveIntegrationTestComponent
|
||||
extends BrambleCoreEagerSingletons {
|
||||
|
||||
ContactManager getContactManager();
|
||||
|
||||
EventBus getEventBus();
|
||||
|
||||
IdentityManager getIdentityManager();
|
||||
|
||||
LifecycleManager getLifecycleManager();
|
||||
|
||||
RemovableDriveManager getRemovableDriveManager();
|
||||
|
||||
class Helper {
|
||||
|
||||
public static void injectEagerSingletons(
|
||||
RemovableDriveIntegrationTestComponent c) {
|
||||
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,81 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
package org.briarproject.bramble.plugin.file;
|
||||
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.WanTcpPluginFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
@Module
|
||||
public class DesktopPluginModule extends PluginModule {
|
||||
class RemovableDriveIntegrationTestModule {
|
||||
|
||||
@Provides
|
||||
PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth,
|
||||
ModemPluginFactory modem, LanTcpPluginFactory lan,
|
||||
WanTcpPluginFactory wan) {
|
||||
@Singleton
|
||||
PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||
return asList(bluetooth, modem, lan, wan);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return singletonList(drive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, List<TransportId>> getTransportPreferences() {
|
||||
// Prefer LAN to Bluetooth
|
||||
return singletonMap(BluetoothConstants.ID,
|
||||
singletonList(LanTcpConstants.ID));
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
};
|
||||
return pluginConfig;
|
||||
}
|
||||
|
||||
@Provides
|
||||
FeatureFlags provideFeatureFlags() {
|
||||
return new FeatureFlags() {
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableImageAttachments() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableProfilePictures() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableDisappearingMessages() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableConnectViaBluetooth() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* A {@link TaskScheduler} for use in tests. The scheduler keeps all scheduled
|
||||
* tasks in a queue until {@link #runTasks()} is called.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class TestTaskScheduler implements TaskScheduler {
|
||||
|
||||
private final Queue<Task> queue = new PriorityBlockingQueue<>();
|
||||
private final Clock clock;
|
||||
|
||||
TestTaskScheduler(Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable schedule(Runnable task, Executor executor, long delay,
|
||||
TimeUnit unit) {
|
||||
AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
return schedule(task, executor, delay, unit, cancelled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
|
||||
long delay, long interval, TimeUnit unit) {
|
||||
AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
return scheduleWithFixedDelay(task, executor, delay, interval, unit,
|
||||
cancelled);
|
||||
}
|
||||
|
||||
private Cancellable schedule(Runnable task, Executor executor, long delay,
|
||||
TimeUnit unit, AtomicBoolean cancelled) {
|
||||
long delayMillis = MILLISECONDS.convert(delay, unit);
|
||||
long dueMillis = clock.currentTimeMillis() + delayMillis;
|
||||
Task t = new Task(task, executor, dueMillis, cancelled);
|
||||
queue.add(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
private Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
|
||||
long delay, long interval, TimeUnit unit, AtomicBoolean cancelled) {
|
||||
// All executions of this periodic task share a cancelled flag
|
||||
Runnable wrapped = () -> {
|
||||
task.run();
|
||||
scheduleWithFixedDelay(task, executor, interval, interval, unit,
|
||||
cancelled);
|
||||
};
|
||||
return schedule(wrapped, executor, delay, unit, cancelled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs any scheduled tasks that are due.
|
||||
*/
|
||||
void runTasks() throws InterruptedException {
|
||||
long now = clock.currentTimeMillis();
|
||||
while (true) {
|
||||
Task t = queue.peek();
|
||||
if (t == null || t.dueMillis > now) return;
|
||||
t = queue.poll();
|
||||
// Submit the task to its executor and wait for it to finish
|
||||
if (!t.run().await(1, MINUTES)) fail();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task
|
||||
implements Cancellable, Comparable<Task> {
|
||||
|
||||
private final Runnable task;
|
||||
private final Executor executor;
|
||||
private final long dueMillis;
|
||||
private final AtomicBoolean cancelled;
|
||||
|
||||
private Task(Runnable task, Executor executor, long dueMillis,
|
||||
AtomicBoolean cancelled) {
|
||||
this.task = task;
|
||||
this.executor = executor;
|
||||
this.dueMillis = dueMillis;
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UseCompareMethod") // Animal Sniffer
|
||||
@Override
|
||||
public int compareTo(Task task) {
|
||||
return Long.valueOf(dueMillis).compareTo(task.dueMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the task to its executor and returns a latch that will be
|
||||
* released when the task finishes.
|
||||
*/
|
||||
public CountDownLatch run() {
|
||||
if (cancelled.get()) return new CountDownLatch(0);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
executor.execute(() -> {
|
||||
task.run();
|
||||
latch.countDown();
|
||||
});
|
||||
return latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelled.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.TaskScheduler;
|
||||
import org.briarproject.bramble.test.SettableClock;
|
||||
import org.briarproject.bramble.test.TimeTravel;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class TimeTravelModule {
|
||||
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
TaskScheduler scheduler;
|
||||
}
|
||||
|
||||
private final ScheduledExecutorService scheduledExecutorService;
|
||||
private final Clock clock;
|
||||
private final TaskScheduler taskScheduler;
|
||||
private final TimeTravel timeTravel;
|
||||
|
||||
public TimeTravelModule() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public TimeTravelModule(boolean travel) {
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ScheduledThreadPoolExecutor.DiscardPolicy();
|
||||
scheduledExecutorService =
|
||||
new ScheduledThreadPoolExecutor(1, policy);
|
||||
if (travel) {
|
||||
// Use a SettableClock and TestTaskScheduler to allow time travel
|
||||
AtomicLong time = new AtomicLong(System.currentTimeMillis());
|
||||
clock = new SettableClock(time);
|
||||
TestTaskScheduler testTaskScheduler = new TestTaskScheduler(clock);
|
||||
taskScheduler = testTaskScheduler;
|
||||
timeTravel = new TimeTravel() {
|
||||
@Override
|
||||
public void setCurrentTimeMillis(long now)
|
||||
throws InterruptedException {
|
||||
time.set(now);
|
||||
testTaskScheduler.runTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCurrentTimeMillis(long add)
|
||||
throws InterruptedException {
|
||||
time.addAndGet(add);
|
||||
testTaskScheduler.runTasks();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Use the default clock and task scheduler
|
||||
clock = new SystemClock();
|
||||
taskScheduler = new TaskSchedulerImpl(scheduledExecutorService);
|
||||
timeTravel = new TimeTravel() {
|
||||
@Override
|
||||
public void setCurrentTimeMillis(long now) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCurrentTimeMillis(long add) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
Clock provideClock() {
|
||||
return clock;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
TaskScheduler provideTaskScheduler(LifecycleManager lifecycleManager) {
|
||||
lifecycleManager.registerForShutdown(scheduledExecutorService);
|
||||
return taskScheduler;
|
||||
}
|
||||
|
||||
@Provides
|
||||
TimeTravel provideTimeTravel() {
|
||||
return timeTravel;
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package org.briarproject.bramble.test;
|
||||
import org.briarproject.bramble.api.FeatureFlags;
|
||||
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
|
||||
import org.briarproject.bramble.event.DefaultEventExecutorModule;
|
||||
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
|
||||
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
|
||||
import org.briarproject.bramble.system.TimeTravelModule;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -12,11 +12,11 @@ import dagger.Provides;
|
||||
@Module(includes = {
|
||||
DefaultBatteryManagerModule.class,
|
||||
DefaultEventExecutorModule.class,
|
||||
DefaultTaskSchedulerModule.class,
|
||||
DefaultWakefulIoExecutorModule.class,
|
||||
TestDatabaseConfigModule.class,
|
||||
TestPluginConfigModule.class,
|
||||
TestSecureRandomModule.class
|
||||
TestSecureRandomModule.class,
|
||||
TimeTravelModule.class
|
||||
})
|
||||
public class BrambleCoreIntegrationTestModule {
|
||||
|
||||
@@ -33,6 +33,16 @@ public class BrambleCoreIntegrationTestModule {
|
||||
public boolean shouldEnableProfilePictures() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableDisappearingMessages() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableConnectViaBluetooth() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -60,8 +59,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
private final ClientId clientId = getClientId();
|
||||
private final long now = System.currentTimeMillis();
|
||||
private final Transaction txn = new Transaction(null, false);
|
||||
private final BdfDictionary groupMeta = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
||||
|
||||
private ClientVersioningManagerImpl createInstance() {
|
||||
context.checking(new Expectations() {{
|
||||
@@ -123,8 +120,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(db).setGroupVisibility(txn, contact.getId(),
|
||||
contactGroup.getId(), SHARED);
|
||||
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
|
||||
groupMeta);
|
||||
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
|
||||
contact.getId());
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
|
||||
@@ -460,9 +457,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
|
||||
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
|
||||
// Get contact ID
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(groupMeta));
|
||||
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||
will(returnValue(contact.getId()));
|
||||
// No states or visibilities have changed
|
||||
}});
|
||||
|
||||
@@ -492,10 +488,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
// Load the latest local update
|
||||
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
|
||||
will(returnValue(oldLocalUpdateBody));
|
||||
// Get client ID
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(groupMeta));
|
||||
// Get contact ID
|
||||
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||
will(returnValue(contact.getId()));
|
||||
// No states or visibilities have changed
|
||||
}});
|
||||
|
||||
@@ -546,8 +541,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
BdfDictionary groupMeta = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||
@@ -577,9 +570,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||
newLocalUpdateMeta, true, false);
|
||||
// The client's visibility has changed
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(groupMeta));
|
||||
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||
will(returnValue(contact.getId()));
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
||||
@@ -619,8 +611,6 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
|
||||
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
|
||||
new BdfEntry(MSG_KEY_LOCAL, true));
|
||||
BdfDictionary groupMeta = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toList(newRemoteUpdate);
|
||||
@@ -650,9 +640,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||
newLocalUpdateMeta, true, false);
|
||||
// The client's visibility has changed
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
contactGroup.getId());
|
||||
will(returnValue(groupMeta));
|
||||
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
|
||||
will(returnValue(contact.getId()));
|
||||
oneOf(db).getContact(txn, contact.getId());
|
||||
will(returnValue(contact));
|
||||
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);
|
||||
|
||||
@@ -8,9 +8,9 @@ import org.briarproject.bramble.system.JavaSystemModule;
|
||||
import dagger.Module;
|
||||
|
||||
@Module(includes = {
|
||||
CircumventionModule.class,
|
||||
JavaNetworkModule.class,
|
||||
JavaSystemModule.class,
|
||||
CircumventionModule.class,
|
||||
SocksModule.class
|
||||
})
|
||||
public class BrambleJavaModule {
|
||||
|
||||
@@ -25,8 +25,8 @@ import static org.briarproject.bramble.util.StringUtils.isValidMac;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class JavaBluetoothPlugin
|
||||
extends BluetoothPlugin<StreamConnection, StreamConnectionNotifier> {
|
||||
class JavaBluetoothPlugin extends
|
||||
AbstractBluetoothPlugin<StreamConnection, StreamConnectionNotifier> {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(JavaBluetoothPlugin.class.getName());
|
||||
@@ -108,6 +108,11 @@ class JavaBluetoothPlugin
|
||||
return null; // TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopDiscoverAndConnect() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
||||
private final File torDirectory;
|
||||
|
||||
@Inject
|
||||
public UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@IoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
|
||||
@@ -23,8 +23,10 @@ import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -45,15 +47,22 @@ import static org.junit.Assume.assumeTrue;
|
||||
public class BridgeTest extends BrambleTestCase {
|
||||
|
||||
@Parameters
|
||||
public static Iterable<String> data() {
|
||||
public static Iterable<Params> data() {
|
||||
BrambleJavaIntegrationTestComponent component =
|
||||
DaggerBrambleJavaIntegrationTestComponent.builder().build();
|
||||
BrambleCoreIntegrationTestEagerSingletons.Helper
|
||||
.injectEagerSingletons(component);
|
||||
return component.getCircumventionProvider().getBridges(false);
|
||||
// Share a failure counter among all the test instances
|
||||
AtomicInteger failures = new AtomicInteger(0);
|
||||
List<String> bridges =
|
||||
component.getCircumventionProvider().getBridges(false);
|
||||
List<Params> states = new ArrayList<>(bridges.size());
|
||||
for (String bridge : bridges) states.add(new Params(bridge, failures));
|
||||
return states;
|
||||
}
|
||||
|
||||
private final static long TIMEOUT = SECONDS.toMillis(60);
|
||||
private final static int NUM_FAILURES_ALLOWED = 1;
|
||||
|
||||
private final static Logger LOG = getLogger(BridgeTest.class.getName());
|
||||
|
||||
@@ -80,11 +89,13 @@ public class BridgeTest extends BrambleTestCase {
|
||||
|
||||
private final File torDir = getTestDirectory();
|
||||
private final String bridge;
|
||||
private final AtomicInteger failures;
|
||||
|
||||
private UnixTorPluginFactory factory;
|
||||
|
||||
public BridgeTest(String bridge) {
|
||||
this.bridge = bridge;
|
||||
public BridgeTest(Params params) {
|
||||
bridge = params.bridge;
|
||||
failures = params.failures;
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -152,10 +163,24 @@ public class BridgeTest extends BrambleTestCase {
|
||||
clock.sleep(500);
|
||||
}
|
||||
if (plugin.getState() != ACTIVE) {
|
||||
fail("Could not connect to Tor within timeout.");
|
||||
LOG.warning("Could not connect to Tor within timeout");
|
||||
if (failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
|
||||
fail(failures.get() + " bridges are unreachable");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
plugin.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Params {
|
||||
|
||||
private final String bridge;
|
||||
private final AtomicInteger failures;
|
||||
|
||||
private Params(String bridge, AtomicInteger failures) {
|
||||
this.bridge = bridge;
|
||||
this.failures = failures;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
briar-android/.gitignore
vendored
4
briar-android/.gitignore
vendored
@@ -10,4 +10,6 @@ src/main/res/values-iw
|
||||
/fastlane/metadata/android/screenshots.html
|
||||
/fastlane/metadata/android/*/images
|
||||
/fastlane/report.xml
|
||||
/fastlane/README.md
|
||||
/fastlane/README.md
|
||||
|
||||
/fastlane/metadata/android/*/changelogs
|
||||
@@ -9,3 +9,19 @@ source_lang = en
|
||||
type = ANDROID
|
||||
minimum_perc = 80
|
||||
|
||||
[briar.google-play-short-description]
|
||||
# https://support.google.com/googleplay/android-developer/answer/9844778?hl=en#zippy=%2Cview-list-of-available-languages
|
||||
lang_map = en: en-US, de: de-DE, es: es-ES, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
|
||||
file_filter = fastlane/metadata/android/<lang>/short_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/short_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
|
||||
[briar.google-play-full-description]
|
||||
lang_map = en: en-US, de: de-DE, es: es-ES, gl: gl-ES, tr: tr-TR, zh-Hans: zh-CN
|
||||
file_filter = fastlane/metadata/android/<lang>/full_description.txt
|
||||
source_file = fastlane/metadata/android/en-US/full_description.txt
|
||||
source_lang = en
|
||||
type = TXT
|
||||
minimum_perc = 100
|
||||
|
||||
@@ -17,7 +17,7 @@ def getStdout = { command, defaultValue ->
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
buildToolsVersion '30.0.3'
|
||||
|
||||
packagingOptions {
|
||||
doNotStrip '**/*.so'
|
||||
@@ -25,9 +25,9 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 10219
|
||||
versionName "1.2.19"
|
||||
targetSdkVersion 30
|
||||
versionCode 10303
|
||||
versionName "1.3.3"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -80,6 +80,7 @@ android {
|
||||
}
|
||||
|
||||
testOptions {
|
||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
@@ -148,6 +149,8 @@ dependencies {
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestUtil 'androidx.test:orchestrator:1.3.0'
|
||||
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
|
||||
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
androidTestImplementation 'junit:junit:4.13.1'
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
json_key_file(ENV["BRIAR_GOOGLE_PLAY_JSON_KEY_FILE"])
|
||||
package_name("org.briarproject.briar.android")
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# Uncomment the line if you want fastlane to automatically update itself
|
||||
# update_fastlane
|
||||
|
||||
opt_out_usage
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
@@ -24,6 +26,21 @@ platform :android do
|
||||
system './demo-mode-deactivate.sh'
|
||||
system './rename_screenshots.py'
|
||||
end
|
||||
|
||||
desc "Updates Google Play metadata (title, descriptions, video, etc.)"
|
||||
lane :metadata do
|
||||
system './update-metadata.sh'
|
||||
upload_to_play_store(
|
||||
skip_upload_apk: true,
|
||||
skip_upload_aab: true,
|
||||
skip_upload_metadata: false,
|
||||
skip_upload_changelogs: true,
|
||||
skip_upload_images: true,
|
||||
skip_upload_screenshots: true,
|
||||
validate_only: false,
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
El Briar és una aplicació de missatgeria dissenyada per a activistes, periodistes i qualsevol persona en general que necessiti una manera segura, fàcil i robsuta de comunicar-se. Al contrari que les aplicacions tradicionals, el Briar no depèn d'un servidor central: els missatges se sincronitzen directament entre els dispositius dels usuaris. Si Internet cau, el Briar pot continuar funcionant mitjançant Bluetooth o Wi-Fi, mantenint el flux fins i tot amb aquesta situació caòtica. Amb un funcionament normal d'Internet, el Briar es pot sincronitzar a través de la xarxa Tor, protegint els usuaris i les seves comunicacions d'ulls indiscrets.
|
||||
|
||||
L'aplicació té com a característica principal l'enviament de missatges privats en grups, fòrums o blocs, atès que es basa en la xarxa Tor. Qualsevol interacció al Briar s'emmagatzema només al vostre dispositiu fins que vulgueu compartir-ho amb altres usuaris.
|
||||
|
||||
No hi han anuncis ni seguiment El codi font d'aquesta aplicació està disponible per a tothom que vulgui inspeccionar-ho, com ja l'han auditat empreses professionals. Totes les versions del Briar són reproduïbles, la qual cosa permet verificar que el codi font publicat coincideix exactament amb l'aplicació publicada aquí. El desenvolupament és a càrrec d'un petit equip sense ànim lucratiu.
|
||||
@@ -0,0 +1 @@
|
||||
Missatgeria segura, arreu.
|
||||
1
briar-android/fastlane/metadata/android/ca/title.txt
Normal file
1
briar-android/fastlane/metadata/android/ca/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar ist eine Messaging-App für Aktivisten, Journalisten und jeden, der eine sichere, einfache und robuste Art der Kommunikation benötigt. Im Gegensatz zu herkömmlichen Messaging-Apps benötigt Briar keinen zentralen Server. Nachrichten werden direkt zwischen den Endgeräten der Benutzer ausgetauscht. Wenn das Internet ausfällt, kann Briar diese auch über Bluetooth oder WLAN austauschen, um den Informationsaustausch in einer Krise aufrecht zu erhalten. Mit einer Internet Verbindung synchronisiert sich Briar über das Tor-Netzwerk und schützt so die Benutzer und ihre Kontakte vor Überwachung.
|
||||
|
||||
Die App bietet private Nachrichten, Gruppen und Foren sowie Blogs. Die Unterstützung für das Tor-Netzwerk ist in die App integriert. Alles, was du in Briar machst, wird nur auf deinem Gerät gespeichert, es sei denn, du entscheidest dich, es mit anderen Benutzern zu teilen.
|
||||
|
||||
Es gibt keine Werbung und kein Tracking. Der Quellcode der App ist komplett offen für jeden einsehbar und wurde bereits professionell auditiert. Alle Versionen von Briar sind reproduzierbar, so dass überprüft werden kann, ob der veröffentlichte Quellcode genau mit der hier veröffentlichten App übereinstimmt. Die Entwicklung wird von einem kleinen Non-Profit-Team durchgeführt.
|
||||
@@ -0,0 +1 @@
|
||||
Sicher kommunizieren, überall
|
||||
1
briar-android/fastlane/metadata/android/de-DE/title.txt
Normal file
1
briar-android/fastlane/metadata/android/de-DE/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging apps, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
|
||||
|
||||
The app features private messages, groups and forums as well as blogs. Support for Tor network is built into the app. Everything you do in Briar is only stored on your device unless you decide to share it with other users.
|
||||
|
||||
There are no advertisements and no tracking. The source code of the app is completely open for anyone to inspect and has already been professionally audited. All releases of Briar are reproducible, making it possible to verify that the published source code matches exactly the app published here. Development is done by a small non-profit team.
|
||||
@@ -0,0 +1 @@
|
||||
Secure messaging, anywhere.
|
||||
1
briar-android/fastlane/metadata/android/en-US/title.txt
Normal file
1
briar-android/fastlane/metadata/android/en-US/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
1
briar-android/fastlane/metadata/android/en-US/video.txt
Normal file
1
briar-android/fastlane/metadata/android/en-US/video.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://www.youtube.com/watch?v=ark7Y4u__SM
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar es una aplicación de mensajería diseñada para activistas, periodistas y cualquier otra persona que necesita una forma segura, fácil y robusta de comunicación. A diferencia de las aplicaciones de mensajería tradicionales, Briar no necesita un servidor central – los mensajes son sincronizados directamente entre los dispositivos de los usuarios. Si no hay conexión a internet disponible, Briar puede sincronizarse vía Bluetooth o WiFi, manteniendo la información fluyendo en crisis. Si Internet está disponible, Briar puede sincronizarse vía red Tor, protegiendo a los usuarios y sus relaciones de la vigilancia.
|
||||
|
||||
La aplicación tiene como características mensajes, grupos y foros privados, como así también blogs. El soporte para red Tor está incorporado en la aplicación. Todo lo que haces en Briar solamente es almacenado en tu dispositivo, a menos que decidas compartirlo con otros usuarios.
|
||||
|
||||
No hay publicidades ni rastreo. El código fuente de la aplicación está completamente abierto para que cualquiera lo inspeccione, y ya ha sido auditado profesionalmente. Todas las versiones de Briar son reproducibles, haciendo posible verificar que el código fuente publicado se corresponda exactamente con la aplicación publicada aquí. El desarrollo es realizado por un reducido equipo sin fines de lucro.
|
||||
@@ -0,0 +1 @@
|
||||
Mensajería segura, en cualquier lado.
|
||||
1
briar-android/fastlane/metadata/android/es-ES/title.txt
Normal file
1
briar-android/fastlane/metadata/android/es-ES/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
1
briar-android/fastlane/metadata/android/es-ES/video.txt
Normal file
1
briar-android/fastlane/metadata/android/es-ES/video.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://www.youtube.com/watch?v=OuJjWdDfKuY
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar یک برنامه پیام رسان می باشد که برای فعالان، روزنامه نگاران و هر کسی که نیازمند یک راه امن، راحت و پیشرفته برای ارتباط با دیگران است می باشد. برخلاف برنامه های پیامرسان مرسوم، Briar به سرور متمرکز اتکا ندارد - پیام ها به صورت مستقیم بین دستگاه کاربران همگام می شود. اگر اینترنت کار نکند، Briar میتواند از طریق بلوتوث یا وای-فای همگام سازی کرده، جریان اطلاعات را در زمان بحران نگه دارد. اگر اینترنت کار کند، Briar میتواند برای محافظت کاربران و وابط آن ها از از شنود، از طریق شبکه تور همگام سازی کند.
|
||||
|
||||
این برنامه امکان ارسال پیام خصوصی، گروهی، تالارهای گفتمان و وبلاگ را در اختیار شما میگذارد. در این برنامه از شبکهی تور پشتیبانی میشود. هر آنچه در برایر (Briar) انجام میدهید، تنها در دستگاه شما ذخیره میشود مگر اینکه خودتان آن را با دیگران به اشتراک بگذارید.
|
||||
|
||||
این برنامه هیچ تبلیغ و ردیابی از کاربران خود انجام نمیدهد. کد منبع برنامه به صورت کاملا باز در اختیار همه قرار دارد و کاملا حرفهای بررسی شده است. تمامی نسخههای جدید برایر (Briar) قابل تولید مجدد هستند که امکان تائید یکسان بودن برنامه منتشر شده با کد منبع را میدهد. توسعه این برنامه توسط یک تیم غیرانتفاعی کوچک صورت میپذیرد.
|
||||
@@ -0,0 +1 @@
|
||||
پیامرسان امن، در هر کجا
|
||||
1
briar-android/fastlane/metadata/android/fa/title.txt
Normal file
1
briar-android/fastlane/metadata/android/fa/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Briar
|
||||
1
briar-android/fastlane/metadata/android/fa/video.txt
Normal file
1
briar-android/fastlane/metadata/android/fa/video.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://www.youtube.com/watch?v=ogDywPyPrB4
|
||||
@@ -0,0 +1,5 @@
|
||||
Briar é unha app de mensaxería deseñada para activistas, xornalistas e calquera que precise comunicarse de xeito robusto, simple e seguro. Ao contrario que as apps de mensaxería tradicionais, Briar non depende dun servidor central - as mensaxes sincronízanse directamente entre os dispositivos das persoas usuarias. Se caese internet, Briar pode sincronizar a través de Bluetooth ou Wi-Fi, mantendo o fluxo de información en períodos de crise. Se hai internet, Briar pode sincronizar a través da rede Tor, protexendo ás usuarias e ás súas relacións da vixilancia.
|
||||
|
||||
A app proporciona mensaxes privadas, grupos e foros así como blogs. O soporte para a rede Tor está incluído na propia app. Todo o que fas en Briar só se garda no teu dispositivo a non ser que decidas compartilo con outras usuarias.
|
||||
|
||||
Non hai publicidade nen rastrexo da túa actividade. O código fonte da app está completamente aberto a calquera que queira revisalo e xa foi auditado profesionalmente. Todas as versións de Briar son reproducibles, facendo posible verificar que o código fonte publicado coincide exactamente coa app publicada aquí. O desenvolvemento realízase por un pequeno equipo de voluntarias.
|
||||
@@ -0,0 +1 @@
|
||||
Mensaxería segura, en todo lugar.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user