mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 21:59:54 +01:00
Compare commits
373 Commits
integratio
...
2085-trans
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67593e2ad0 | ||
|
|
d4c1e132f7 | ||
|
|
6b976df6a8 | ||
|
|
3e4db3b9da | ||
|
|
0bf59eec20 | ||
|
|
9f828a2222 | ||
|
|
7be77b8c60 | ||
|
|
d5853e8403 | ||
|
|
32e9bf01ec | ||
|
|
a5ce400341 | ||
|
|
a960bfb2c1 | ||
|
|
847650f280 | ||
|
|
77a3199aac | ||
|
|
9a58b37ce2 | ||
|
|
608e1eac6b | ||
|
|
09de768e7e | ||
|
|
faab80f0ea | ||
|
|
07162cad8b | ||
|
|
ac0fc21e6e | ||
|
|
dab736ce0e | ||
|
|
bcbc96dc2d | ||
|
|
a72e92de24 | ||
|
|
1ddcd6cfff | ||
|
|
5dfd9e3546 | ||
|
|
e05575b956 | ||
|
|
fd810f5c16 | ||
|
|
3f5e131250 | ||
|
|
3ee516599d | ||
|
|
c703d90636 | ||
|
|
e228b9fcbf | ||
|
|
6e6cadd3ad | ||
|
|
9cc8d44778 | ||
|
|
ee6f571c31 | ||
|
|
2ac3bdd3ae | ||
|
|
e35ffe0cf0 | ||
|
|
8a04d8edc4 | ||
|
|
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 | ||
|
|
c2a4b5e26a | ||
|
|
feac0ad802 | ||
|
|
60478eba3f | ||
|
|
3639952612 | ||
|
|
c4a654b267 | ||
|
|
ecb31a4d32 | ||
|
|
76f201bb2f | ||
|
|
1d44305e34 | ||
|
|
a37af592cd | ||
|
|
87799b743c | ||
|
|
b898a7c370 | ||
|
|
7f486eef4c | ||
|
|
f3210e3af2 | ||
|
|
225fd6fd49 | ||
|
|
400d259a60 | ||
|
|
4074ac8578 | ||
|
|
b2e6dd4138 | ||
|
|
b608b42174 | ||
|
|
f603254153 | ||
|
|
c851dd228b | ||
|
|
e97478a21a | ||
|
|
726ebcea3f | ||
|
|
2f969775d8 | ||
|
|
d3b855318c | ||
|
|
95104d3383 | ||
|
|
6860a04e8b | ||
|
|
33c24f8655 | ||
|
|
1fa4b78474 | ||
|
|
b678de7529 | ||
|
|
ab1ed0ff5a | ||
|
|
ad20e5230a | ||
|
|
bcc0442add | ||
|
|
700f6e05bf | ||
|
|
d8327d6de2 | ||
|
|
5a55b3d7e3 | ||
|
|
bed87ed439 | ||
|
|
6d1f1c7852 | ||
|
|
f6b3bde724 | ||
|
|
94ec22bef8 | ||
|
|
ae923e5777 | ||
|
|
46b4204805 | ||
|
|
2257c005b3 | ||
|
|
eb9ff9c954 | ||
|
|
4f08f81779 | ||
|
|
2b0815aaac | ||
|
|
a9e83491d3 | ||
|
|
ee967c5d8f | ||
|
|
43740777d4 | ||
|
|
d5b0556ea2 | ||
|
|
227f00c10c | ||
|
|
8b4ff2dc8a | ||
|
|
4be2afb915 | ||
|
|
74447b8ec3 | ||
|
|
d95242bd7e | ||
|
|
51794424ce | ||
|
|
5db099bae6 | ||
|
|
a2faa3bd3b | ||
|
|
a3fb7b5680 | ||
|
|
264d110dbd | ||
|
|
839b871a45 | ||
|
|
2fb4825b8f | ||
|
|
3f9a66b1b6 | ||
|
|
d796916387 | ||
|
|
fe07b760ea | ||
|
|
b4a5fe6772 | ||
|
|
e21e6267d7 | ||
|
|
d7afbdf690 | ||
|
|
c5d2661c1d | ||
|
|
b738bdd14e | ||
|
|
6b61725c6a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ local.properties
|
|||||||
|
|
||||||
# Android Studio
|
# Android Studio
|
||||||
.idea/*
|
.idea/*
|
||||||
|
!.idea/inspectionProfiles/
|
||||||
!.idea/runConfigurations/
|
!.idea/runConfigurations/
|
||||||
!.idea/codeStyleSettings.xml
|
!.idea/codeStyleSettings.xml
|
||||||
!.idea/codeStyles
|
!.idea/codeStyles
|
||||||
|
|||||||
@@ -1,30 +1,79 @@
|
|||||||
image: briar/ci-image-android:latest
|
image: briar/ci-image-android:latest
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
- optional_tests
|
- optional_tests
|
||||||
- check_reproducibility
|
- check_reproducibility
|
||||||
|
|
||||||
test:
|
workflow:
|
||||||
stage: test
|
# 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:
|
before_script:
|
||||||
- set -e
|
- set -e
|
||||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
|
key: "$CI_COMMIT_REF_SLUG"
|
||||||
paths:
|
paths:
|
||||||
- .gradle/wrapper
|
- .gradle/wrapper
|
||||||
- .gradle/caches
|
- .gradle/caches
|
||||||
|
|
||||||
script:
|
|
||||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
|
||||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources
|
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
# these file change every time but should not be cached
|
# these file change every time and should not be cached
|
||||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||||
|
|
||||||
|
test:
|
||||||
|
extends: .base-test
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||||
|
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
|
when: always
|
||||||
|
- when: always
|
||||||
|
|
||||||
|
android test:
|
||||||
|
extends: .base-test
|
||||||
|
stage: optional_tests
|
||||||
|
image: briar/ci-image-android-emulator:latest
|
||||||
|
script:
|
||||||
|
# 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.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
|
||||||
|
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
|
||||||
|
|
||||||
test_reproducible:
|
test_reproducible:
|
||||||
stage: check_reproducibility
|
stage: check_reproducibility
|
||||||
@@ -40,6 +89,7 @@ test_reproducible:
|
|||||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
|
key: "$CI_COMMIT_REF_SLUG"
|
||||||
paths:
|
paths:
|
||||||
- .gradle/wrapper
|
- .gradle/wrapper
|
||||||
- .gradle/caches
|
- .gradle/caches
|
||||||
@@ -52,11 +102,15 @@ test_reproducible:
|
|||||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||||
|
|
||||||
manual_tests:
|
bridge test:
|
||||||
extends: .optional_tests
|
extends: .optional_tests
|
||||||
when: manual
|
rules:
|
||||||
except:
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
- tags
|
when: on_success
|
||||||
|
allow_failure: true
|
||||||
|
- if: '$CI_COMMIT_TAG == null'
|
||||||
|
when: manual
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
pre_release_tests:
|
pre_release_tests:
|
||||||
extends: .optional_tests
|
extends: .optional_tests
|
||||||
|
|||||||
1
.idea/dictionaries/briar.xml
generated
1
.idea/dictionaries/briar.xml
generated
@@ -7,6 +7,7 @@
|
|||||||
<w>encrypter</w>
|
<w>encrypter</w>
|
||||||
<w>identicon</w>
|
<w>identicon</w>
|
||||||
<w>introducee</w>
|
<w>introducee</w>
|
||||||
|
<w>introducees</w>
|
||||||
<w>introducer</w>
|
<w>introducer</w>
|
||||||
<w>onboarding</w>
|
<w>onboarding</w>
|
||||||
</words>
|
</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>
|
||||||
51
.idea/runConfigurations/Instrumentation_Tests__destroys_DB_.xml
generated
Normal file
51
.idea/runConfigurations/Instrumentation_Tests__destroys_DB_.xml
generated
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Instrumentation Tests (destroys DB)" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
|
||||||
|
<module name="briar.briar-android" />
|
||||||
|
<option name="TESTING_TYPE" value="1" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="CLASS_NAME" value="" />
|
||||||
|
<option name="PACKAGE_NAME" value="org.briarproject.briar.android" />
|
||||||
|
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
|
||||||
|
<option name="EXTRA_OPTIONS" value="-e notAnnotation androidx.test.filters.LargeTest" />
|
||||||
|
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
|
||||||
|
<option name="CLEAR_LOGCAT" value="false" />
|
||||||
|
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||||
|
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
|
||||||
|
<option name="FORCE_STOP_RUNNING_APP" value="true" />
|
||||||
|
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||||
|
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||||
|
<Auto>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
</Auto>
|
||||||
|
<Hybrid>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
</Hybrid>
|
||||||
|
<Java />
|
||||||
|
<Native>
|
||||||
|
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||||
|
<option name="SHOW_STATIC_VARS" value="true" />
|
||||||
|
<option name="WORKING_DIR" value="" />
|
||||||
|
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||||
|
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||||
|
</Native>
|
||||||
|
<Profilers>
|
||||||
|
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
|
||||||
|
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||||
|
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||||
|
</Profilers>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -6,13 +6,17 @@ apply from: 'witness.gradle'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
buildToolsVersion '30.0.2'
|
buildToolsVersion '30.0.3'
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
doNotStrip '**/*.so'
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 10216
|
versionCode 10303
|
||||||
versionName "1.2.16"
|
versionName "1.3.3"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
|||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class AndroidBluetoothPlugin
|
class AndroidBluetoothPlugin extends
|
||||||
extends BluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
|
AbstractBluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(AndroidBluetoothPlugin.class.getName());
|
getLogger(AndroidBluetoothPlugin.class.getName());
|
||||||
@@ -75,6 +75,7 @@ class AndroidBluetoothPlugin
|
|||||||
|
|
||||||
// Non-null if the plugin started successfully
|
// Non-null if the plugin started successfully
|
||||||
private volatile BluetoothAdapter adapter = null;
|
private volatile BluetoothAdapter adapter = null;
|
||||||
|
private volatile boolean stopDiscoverAndConnect;
|
||||||
|
|
||||||
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||||
BluetoothConnectionFactory<BluetoothSocket> connectionFactory,
|
BluetoothConnectionFactory<BluetoothSocket> connectionFactory,
|
||||||
@@ -175,6 +176,11 @@ class AndroidBluetoothPlugin
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
IoUtils.tryToClose(s, LOG, WARNING);
|
IoUtils.tryToClose(s, LOG, WARNING);
|
||||||
throw e;
|
throw e;
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
// BluetoothSocket#connect() may throw an NPE under unknown
|
||||||
|
// circumstances
|
||||||
|
IoUtils.tryToClose(s, LOG, WARNING);
|
||||||
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,22 +188,40 @@ class AndroidBluetoothPlugin
|
|||||||
@Nullable
|
@Nullable
|
||||||
DuplexTransportConnection discoverAndConnect(String uuid) {
|
DuplexTransportConnection discoverAndConnect(String uuid) {
|
||||||
if (adapter == null) return null;
|
if (adapter == null) return null;
|
||||||
for (String address : discoverDevices()) {
|
if (!discoverSemaphore.tryAcquire()) {
|
||||||
try {
|
LOG.info("Discover already running");
|
||||||
if (LOG.isLoggable(INFO))
|
return null;
|
||||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
}
|
||||||
return connectTo(address, uuid);
|
try {
|
||||||
} catch (IOException e) {
|
stopDiscoverAndConnect = false;
|
||||||
if (LOG.isLoggable(INFO)) {
|
for (String address : discoverDevices()) {
|
||||||
LOG.info("Could not connect to "
|
if (stopDiscoverAndConnect) {
|
||||||
+ scrubMacAddress(address));
|
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");
|
LOG.info("Could not connect to any devices");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopDiscoverAndConnect() {
|
||||||
|
stopDiscoverAndConnect = true;
|
||||||
|
adapter.cancelDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
private Collection<String> discoverDevices() {
|
private Collection<String> discoverDevices() {
|
||||||
List<String> addresses = new ArrayList<>();
|
List<String> addresses = new ArrayList<>();
|
||||||
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
|
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
AndroidExecutor androidExecutor,
|
AndroidExecutor androidExecutor,
|
||||||
AndroidWakeLockManager wakeLockManager,
|
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;
|
private final Application app;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
EventBus eventBus,
|
EventBus eventBus,
|
||||||
BackoffFactory backoffFactory,
|
BackoffFactory backoffFactory,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final File torDirectory;
|
private final File torDirectory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||||
Application app,
|
Application app,
|
||||||
NetworkManager networkManager,
|
NetworkManager networkManager,
|
||||||
|
|||||||
@@ -114,12 +114,8 @@ public class AndroidUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of supported content types for image attachments.
|
* Returns an array of supported content types for image attachments.
|
||||||
* GIFs can't be compressed on API < 24 so they're not supported.
|
|
||||||
* <p>
|
|
||||||
* TODO: Remove this restriction when large message support is added
|
|
||||||
*/
|
*/
|
||||||
public static String[] getSupportedImageContentTypes() {
|
public static String[] getSupportedImageContentTypes() {
|
||||||
if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"};
|
return new String[] {"image/jpeg", "image/png", "image/gif"};
|
||||||
else return new String[] {"image/jpeg", "image/png", "image/gif"};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,28 +32,31 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once for each incoming message that passes validation.
|
* Called once for each incoming message that passes validation.
|
||||||
|
* <p>
|
||||||
|
* If an unexpected exception occurs while handling data that is assumed
|
||||||
|
* to be valid (e.g. locally created metadata), it may be sensible to
|
||||||
|
* rethrow the unexpected exception as a DbException so that delivery is
|
||||||
|
* attempted again at next startup. This will allow delivery to succeed if
|
||||||
|
* the unexpected exception was caused by a bug that has subsequently been
|
||||||
|
* fixed.
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @return Whether or not this message should be shared
|
* @throws DbException if a database error occurs while delivering the
|
||||||
* @throws DbException Should only be used for real database errors.
|
* message. Delivery will be attempted again at next startup. Throwing
|
||||||
* If this is thrown, delivery will be attempted again at next startup,
|
* this exception has the same effect as returning
|
||||||
* whereas if a FormatException is thrown, the message will be permanently
|
* {@link DeliveryAction#DEFER}.
|
||||||
* invalidated.
|
* @throws FormatException if the message is invalid in the context of its
|
||||||
* @throws FormatException Use this for any non-database error
|
* dependencies. The message and any dependents will be marked as invalid
|
||||||
* that occurs while handling remotely created data.
|
* and deleted along with their metadata. Throwing this exception has the
|
||||||
* This includes errors that occur while handling locally created data
|
* same effect as returning {@link DeliveryAction#REJECT}.
|
||||||
* in a context controlled by remotely created data
|
|
||||||
* (for example, parsing the metadata of a dependency
|
|
||||||
* of an incoming message).
|
|
||||||
* Never rethrow DbException as FormatException!
|
|
||||||
*/
|
*/
|
||||||
protected abstract boolean incomingMessage(Transaction txn, Message m,
|
protected abstract DeliveryAction incomingMessage(Transaction txn,
|
||||||
BdfList body, BdfDictionary meta) throws DbException,
|
Message m, BdfList body, BdfDictionary meta)
|
||||||
FormatException;
|
throws DbException, FormatException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||||
throws DbException, InvalidMessageException {
|
Metadata meta) throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
BdfList body = clientHelper.toList(m);
|
BdfList body = clientHelper.toList(m);
|
||||||
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
BdfDictionary metaDictionary = metadataParser.parse(meta);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.api.client;
|
package org.briarproject.bramble.api.client;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
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.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
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 org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -50,9 +52,11 @@ public interface ClientHelper {
|
|||||||
BdfDictionary getGroupMetadataAsDictionary(Transaction txn, GroupId g)
|
BdfDictionary getGroupMetadataAsDictionary(Transaction txn, GroupId g)
|
||||||
throws DbException, FormatException;
|
throws DbException, FormatException;
|
||||||
|
|
||||||
|
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
|
||||||
|
BdfDictionary query) throws DbException, FormatException;
|
||||||
|
|
||||||
BdfDictionary getMessageMetadataAsDictionary(MessageId m)
|
BdfDictionary getMessageMetadataAsDictionary(MessageId m)
|
||||||
throws DbException,
|
throws DbException, FormatException;
|
||||||
FormatException;
|
|
||||||
|
|
||||||
BdfDictionary getMessageMetadataAsDictionary(Transaction txn, MessageId m)
|
BdfDictionary getMessageMetadataAsDictionary(Transaction txn, MessageId m)
|
||||||
throws DbException, FormatException;
|
throws DbException, FormatException;
|
||||||
@@ -119,4 +123,17 @@ public interface ClientHelper {
|
|||||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||||
BdfDictionary properties) throws FormatException;
|
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
|
@NotNullByDefault
|
||||||
public interface DatabaseComponent extends TransactionManager {
|
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.
|
* Opens the database and returns true if the database already existed.
|
||||||
*
|
*
|
||||||
@@ -106,6 +118,18 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
|
KeySetId addTransportKeys(Transaction txn, PendingContactId p,
|
||||||
TransportKeys k) throws DbException;
|
TransportKeys k) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are any acks or messages to send to the given
|
||||||
|
* contact over a transport with the given maximum latency.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*
|
||||||
|
* @param eager True if messages that are not yet due for retransmission
|
||||||
|
* should be included
|
||||||
|
*/
|
||||||
|
boolean containsAnythingToSend(Transaction txn, ContactId c,
|
||||||
|
int maxLatency, boolean eager) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
* local pseudonym.
|
* local pseudonym.
|
||||||
@@ -138,6 +162,16 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
boolean containsPendingContact(Transaction txn, PendingContactId p)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the database contains keys for communicating with the
|
||||||
|
* given contact over the given transport. Handshake mode and rotation mode
|
||||||
|
* keys are included, whether activated or not.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
boolean containsTransportKeys(Transaction txn, ContactId c, TransportId t)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the message with the given ID. Unlike
|
* Deletes the message with the given ID. Unlike
|
||||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
* {@link #removeMessage(Transaction, MessageId)}, the message ID,
|
||||||
@@ -168,6 +202,18 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||||
int maxLength, int maxLatency) throws DbException;
|
int maxLength, int maxLatency) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a batch of messages for the given contact containing the
|
||||||
|
* messages with the given IDs, for transmission over a transport with
|
||||||
|
* the given maximum latency.
|
||||||
|
* <p/>
|
||||||
|
* If any of the given messages are not in the database or are not visible
|
||||||
|
* to the contact, they are omitted from the batch without throwing an
|
||||||
|
* exception.
|
||||||
|
*/
|
||||||
|
Collection<Message> generateBatch(Transaction txn, ContactId c,
|
||||||
|
Collection<MessageId> ids, int maxLatency) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an offer for the given contact for transmission over a
|
* Returns an offer for the given contact for transmission over a
|
||||||
* transport with the given maximum latency, or null if there are no
|
* transport with the given maximum latency, or null if there are no
|
||||||
@@ -288,6 +334,16 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
|
Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
|
||||||
throws DbException;
|
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.
|
* Returns the IDs of any messages that need to be validated.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -314,6 +370,15 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<MessageId> getMessagesToShare(Transaction txn)
|
Collection<MessageId> getMessagesToShare(Transaction txn)
|
||||||
throws DbException;
|
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.
|
* Returns the metadata for all delivered messages in the given group.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -395,6 +460,36 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the IDs of all messages that are eligible to be sent to the
|
||||||
|
* given contact, together with their raw lengths. This may include
|
||||||
|
* messages that have already been sent and are not yet due for
|
||||||
|
* retransmission.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
|
||||||
|
ContactId c) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total length, including headers, of all messages that are
|
||||||
|
* eligible to be sent to the given contact. This may include messages
|
||||||
|
* that have already been sent and are not yet due for retransmission.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
long getUnackedMessageBytesToSend(Transaction txn, ContactId c)
|
||||||
|
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
|
* 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
|
* message is due to be sent to the given contact. The returned value may
|
||||||
@@ -443,6 +538,16 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
|
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contact IDs and transport IDs for which the DB contains
|
||||||
|
* at least one set of transport keys. Handshake mode and rotation mode
|
||||||
|
* keys are included, whether activated or not.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
|
||||||
|
Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given transport keys.
|
* Increments the outgoing stream counter for the given transport keys.
|
||||||
*/
|
*/
|
||||||
@@ -535,6 +640,13 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
||||||
throws DbException;
|
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.
|
* Marks the given contact as verified.
|
||||||
*/
|
*/
|
||||||
@@ -557,6 +669,12 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
void setMessagePermanent(Transaction txn, MessageId m) throws DbException;
|
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.
|
* Marks the given message as shared.
|
||||||
*/
|
*/
|
||||||
@@ -599,6 +717,22 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
|
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
|
||||||
throws DbException;
|
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.
|
* 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.
|
* Returns the author's name.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public interface TransportConnectionWriter {
|
|||||||
*/
|
*/
|
||||||
int getMaxIdleTime();
|
int getMaxIdleTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the transport is lossy and cheap.
|
||||||
|
*/
|
||||||
|
boolean isLossyAndCheap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an output stream for writing to the transport connection.
|
* Returns an output stream for writing to the transport connection.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ public abstract class AbstractDuplexTransportConnection
|
|||||||
return plugin.getMaxIdleTime();
|
return plugin.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLossyAndCheap() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() throws IOException {
|
public OutputStream getOutputStream() throws IOException {
|
||||||
return AbstractDuplexTransportConnection.this.getOutputStream();
|
return AbstractDuplexTransportConnection.this.getOutputStream();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.bramble.api.plugin;
|
package org.briarproject.bramble.api.plugin.file;
|
||||||
|
|
||||||
public interface FileConstants {
|
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,46 @@
|
|||||||
|
package org.briarproject.bramble.api.plugin.file;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
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, or null if no reader task
|
||||||
|
* is running.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
RemovableDriveTask getCurrentReaderTask();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently running writer task, or null if no writer task
|
||||||
|
* is running.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
RemovableDriveTask getCurrentWriterTask();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts and returns a reader task, reading from a stream described by
|
||||||
|
* the given transport properties. If a reader task is already running,
|
||||||
|
* it will be returned and the argument will be ignored.
|
||||||
|
*/
|
||||||
|
RemovableDriveTask startReaderTask(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
|
||||||
|
* is already running, it will be returned and the arguments will be
|
||||||
|
* ignored.
|
||||||
|
*/
|
||||||
|
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there is anything to send to the given contact.
|
||||||
|
*/
|
||||||
|
boolean isWriterTaskNeeded(ContactId c) throws DbException;
|
||||||
|
}
|
||||||
@@ -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, or zero if the total is unknown.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,12 @@ import javax.annotation.Nullable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface SimplexPlugin extends Plugin {
|
public interface SimplexPlugin extends Plugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the transport is likely to lose streams and the cost of
|
||||||
|
* transmitting redundant copies of data is cheap.
|
||||||
|
*/
|
||||||
|
boolean isLossyAndCheap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to create and return a reader for the given transport
|
* Attempts to create and return a reader for the given transport
|
||||||
* properties. Returns null if a reader cannot be created.
|
* properties. Returns null if a reader cannot be created.
|
||||||
|
|||||||
@@ -21,5 +21,6 @@ public interface ReportingConstants {
|
|||||||
* Hidden service address for reporting crashes and feedback to the
|
* Hidden service address for reporting crashes and feedback to the
|
||||||
* developers.
|
* developers.
|
||||||
*/
|
*/
|
||||||
String DEV_ONION_ADDRESS = "cwqmubyvnig3wag3.onion";
|
String DEV_ONION_ADDRESS =
|
||||||
|
"b2nkt5doeamvdmjzfz7g42hk5vdtlnktlgzhel2bgjcc4v4jhnx2qrqd.onion";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public interface SyncSessionFactory {
|
|||||||
PriorityHandler handler);
|
PriorityHandler handler);
|
||||||
|
|
||||||
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, StreamWriter streamWriter);
|
int maxLatency, boolean eager, StreamWriter streamWriter);
|
||||||
|
|
||||||
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event {
|
|||||||
|
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final Collection<MessageId> messageIds;
|
private final Collection<MessageId> messageIds;
|
||||||
|
private final long totalLength;
|
||||||
|
|
||||||
public MessagesSentEvent(ContactId contactId,
|
public MessagesSentEvent(ContactId contactId,
|
||||||
Collection<MessageId> messageIds) {
|
Collection<MessageId> messageIds, long totalLength) {
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.messageIds = messageIds;
|
this.messageIds = messageIds;
|
||||||
|
this.totalLength = totalLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactId getContactId() {
|
public ContactId getContactId() {
|
||||||
@@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event {
|
|||||||
public Collection<MessageId> getMessageIds() {
|
public Collection<MessageId> getMessageIds() {
|
||||||
return messageIds;
|
return messageIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getTotalLength() {
|
||||||
|
return totalLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,23 +10,54 @@ public interface IncomingMessageHook {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once for each incoming message that passes validation.
|
* Called once for each incoming message that passes validation.
|
||||||
|
* <p>
|
||||||
|
* If an unexpected exception occurs while handling data that is assumed
|
||||||
|
* to be valid (e.g. locally created metadata), it may be sensible to
|
||||||
|
* rethrow the unexpected exception as a DbException so that delivery is
|
||||||
|
* attempted again at next startup. This will allow delivery to succeed if
|
||||||
|
* the unexpected exception was caused by a bug that has subsequently been
|
||||||
|
* fixed.
|
||||||
*
|
*
|
||||||
* @param txn A read-write transaction
|
* @param txn A read-write transaction
|
||||||
* @return Whether or not this message should be shared
|
* @throws DbException if a database error occurs while delivering the
|
||||||
* @throws DbException Should only be used for real database errors.
|
* message. Delivery will be attempted again at next startup. Throwing
|
||||||
* If this is thrown, delivery will be attempted again at next startup,
|
* this exception has the same effect as returning
|
||||||
* whereas if an InvalidMessageException is thrown,
|
* {@link DeliveryAction#DEFER}.
|
||||||
* the message will be permanently invalidated.
|
* @throws InvalidMessageException if the message is invalid in the context
|
||||||
* @throws InvalidMessageException for any non-database error
|
* of its dependencies. The message and any dependents will be marked as
|
||||||
* that occurs while handling remotely created data.
|
* invalid and deleted along with their metadata. Throwing this exception
|
||||||
* This includes errors that occur while handling locally created data
|
* has the same effect as returning {@link DeliveryAction#REJECT}.
|
||||||
* in a context controlled by remotely created data
|
|
||||||
* (for example, parsing the metadata of a dependency
|
|
||||||
* of an incoming message).
|
|
||||||
* Throwing this will delete the incoming message and its metadata
|
|
||||||
* marking it as invalid in the database.
|
|
||||||
* Never rethrow DbException as InvalidMessageException!
|
|
||||||
*/
|
*/
|
||||||
boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
throws DbException, InvalidMessageException;
|
throws DbException, InvalidMessageException;
|
||||||
|
|
||||||
|
enum DeliveryAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message and any dependent messages will be moved to the
|
||||||
|
* {@link MessageState#INVALID INVALID state} and deleted, along with
|
||||||
|
* their metadata.
|
||||||
|
*/
|
||||||
|
REJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will be moved to the
|
||||||
|
* {@link MessageState#PENDING PENDING state}. Delivery will be
|
||||||
|
* attempted again at next startup.
|
||||||
|
*/
|
||||||
|
DEFER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will be moved to the
|
||||||
|
* {@link MessageState#DELIVERED DELIVERED state} and shared.
|
||||||
|
*/
|
||||||
|
ACCEPT_SHARE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will be moved to the
|
||||||
|
* {@link MessageState#DELIVERED DELIVERED state} and will not be
|
||||||
|
* shared.
|
||||||
|
*/
|
||||||
|
ACCEPT_DO_NOT_SHARE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,33 @@
|
|||||||
package org.briarproject.bramble.api.sync.validation;
|
package org.briarproject.bramble.api.sync.validation;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
|
||||||
|
|
||||||
public enum MessageState {
|
public enum MessageState {
|
||||||
|
|
||||||
UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3);
|
/**
|
||||||
|
* A remote message that has not yet been validated.
|
||||||
|
*/
|
||||||
|
UNKNOWN(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A remote message that has failed validation, has been
|
||||||
|
* {@link DeliveryAction#REJECT rejected} by the local sync client, or
|
||||||
|
* depends on another message that has failed validation or been rejected.
|
||||||
|
*/
|
||||||
|
INVALID(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A remote message that has passed validation and is awaiting delivery to
|
||||||
|
* the local sync client. The message will not be delivered until all its
|
||||||
|
* dependencies have been validated and delivered.
|
||||||
|
*/
|
||||||
|
PENDING(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A local message, or a remote message that has passed validation and
|
||||||
|
* been delivered to the local sync client.
|
||||||
|
*/
|
||||||
|
DELIVERED(3);
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,24 @@ public interface KeyManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives and stores a set of rotation mode transport keys for
|
* Derives and stores a set of rotation mode transport keys for
|
||||||
* communicating with the given contact over each transport and returns the
|
* communicating with the given contact over the given transport and
|
||||||
* key set IDs.
|
* returns the key set ID, or null if the transport is not supported.
|
||||||
|
* <p/>
|
||||||
|
* {@link StreamContext StreamContexts} for the contact can be created
|
||||||
|
* after this method has returned.
|
||||||
|
*
|
||||||
|
* @param alice True if the local party is Alice
|
||||||
|
* @param active Whether the derived keys can be used for outgoing streams
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t,
|
||||||
|
SecretKey rootKey, long timestamp, boolean alice,
|
||||||
|
boolean active) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives and stores a set of rotation mode transport keys for
|
||||||
|
* communicating with the given contact over each supported transport and
|
||||||
|
* returns the key set IDs.
|
||||||
* <p/>
|
* <p/>
|
||||||
* {@link StreamContext StreamContexts} for the contact can be created
|
* {@link StreamContext StreamContexts} for the contact can be created
|
||||||
* after this method has returned.
|
* after this method has returned.
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.briarproject.bramble.api.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface TransportKeyAgreementManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique ID of the transport key agreement client.
|
||||||
|
*/
|
||||||
|
ClientId CLIENT_ID =
|
||||||
|
new ClientId("org.briarproject.bramble.transport.agreement");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current major version of the transport key agreement client.
|
||||||
|
*/
|
||||||
|
int MAJOR_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current minor version of the transport key agreement client.
|
||||||
|
*/
|
||||||
|
int MINOR_VERSION = 0;
|
||||||
|
}
|
||||||
@@ -6,7 +6,9 @@ import org.briarproject.bramble.api.data.BdfList;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ValidationUtils {
|
public class ValidationUtils {
|
||||||
|
|
||||||
@@ -64,4 +66,9 @@ public class ValidationUtils {
|
|||||||
if (dictionary != null && dictionary.size() != size)
|
if (dictionary != null && dictionary.size() != size)
|
||||||
throw new FormatException();
|
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;
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.cleanup.CleanupModule;
|
||||||
import org.briarproject.bramble.contact.ContactModule;
|
import org.briarproject.bramble.contact.ContactModule;
|
||||||
import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||||
@@ -10,10 +11,13 @@ import org.briarproject.bramble.properties.PropertiesModule;
|
|||||||
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
import org.briarproject.bramble.rendezvous.RendezvousModule;
|
||||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
|
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
||||||
import org.briarproject.bramble.versioning.VersioningModule;
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
public interface BrambleCoreEagerSingletons {
|
public interface BrambleCoreEagerSingletons {
|
||||||
|
|
||||||
|
void inject(CleanupModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(ContactModule.EagerSingletons init);
|
void inject(ContactModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(CryptoExecutorModule.EagerSingletons init);
|
void inject(CryptoExecutorModule.EagerSingletons init);
|
||||||
@@ -30,6 +34,8 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
|
|
||||||
void inject(RendezvousModule.EagerSingletons init);
|
void inject(RendezvousModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(TransportKeyAgreementModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(TransportModule.EagerSingletons init);
|
void inject(TransportModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(ValidationModule.EagerSingletons init);
|
void inject(ValidationModule.EagerSingletons init);
|
||||||
@@ -39,6 +45,7 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
class Helper {
|
class Helper {
|
||||||
|
|
||||||
public static void injectEagerSingletons(BrambleCoreEagerSingletons c) {
|
public static void injectEagerSingletons(BrambleCoreEagerSingletons c) {
|
||||||
|
c.inject(new CleanupModule.EagerSingletons());
|
||||||
c.inject(new ContactModule.EagerSingletons());
|
c.inject(new ContactModule.EagerSingletons());
|
||||||
c.inject(new CryptoExecutorModule.EagerSingletons());
|
c.inject(new CryptoExecutorModule.EagerSingletons());
|
||||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||||
@@ -47,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
c.inject(new RendezvousModule.EagerSingletons());
|
c.inject(new RendezvousModule.EagerSingletons());
|
||||||
c.inject(new PluginModule.EagerSingletons());
|
c.inject(new PluginModule.EagerSingletons());
|
||||||
c.inject(new PropertiesModule.EagerSingletons());
|
c.inject(new PropertiesModule.EagerSingletons());
|
||||||
|
c.inject(new TransportKeyAgreementModule.EagerSingletons());
|
||||||
c.inject(new TransportModule.EagerSingletons());
|
c.inject(new TransportModule.EagerSingletons());
|
||||||
c.inject(new ValidationModule.EagerSingletons());
|
c.inject(new ValidationModule.EagerSingletons());
|
||||||
c.inject(new VersioningModule.EagerSingletons());
|
c.inject(new VersioningModule.EagerSingletons());
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble;
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.cleanup.CleanupModule;
|
||||||
import org.briarproject.bramble.client.ClientModule;
|
import org.briarproject.bramble.client.ClientModule;
|
||||||
import org.briarproject.bramble.connection.ConnectionModule;
|
import org.briarproject.bramble.connection.ConnectionModule;
|
||||||
import org.briarproject.bramble.contact.ContactModule;
|
import org.briarproject.bramble.contact.ContactModule;
|
||||||
@@ -21,15 +22,15 @@ import org.briarproject.bramble.rendezvous.RendezvousModule;
|
|||||||
import org.briarproject.bramble.settings.SettingsModule;
|
import org.briarproject.bramble.settings.SettingsModule;
|
||||||
import org.briarproject.bramble.sync.SyncModule;
|
import org.briarproject.bramble.sync.SyncModule;
|
||||||
import org.briarproject.bramble.sync.validation.ValidationModule;
|
import org.briarproject.bramble.sync.validation.ValidationModule;
|
||||||
import org.briarproject.bramble.system.ClockModule;
|
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
|
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
|
||||||
import org.briarproject.bramble.versioning.VersioningModule;
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
|
||||||
@Module(includes = {
|
@Module(includes = {
|
||||||
|
CleanupModule.class,
|
||||||
ClientModule.class,
|
ClientModule.class,
|
||||||
ClockModule.class,
|
|
||||||
ConnectionModule.class,
|
ConnectionModule.class,
|
||||||
ContactModule.class,
|
ContactModule.class,
|
||||||
CryptoModule.class,
|
CryptoModule.class,
|
||||||
@@ -49,6 +50,7 @@ import dagger.Module;
|
|||||||
RendezvousModule.class,
|
RendezvousModule.class,
|
||||||
SettingsModule.class,
|
SettingsModule.class,
|
||||||
SyncModule.class,
|
SyncModule.class,
|
||||||
|
TransportKeyAgreementModule.class,
|
||||||
TransportModule.class,
|
TransportModule.class,
|
||||||
ValidationModule.class,
|
ValidationModule.class,
|
||||||
VersioningModule.class
|
VersioningModule.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.FormatException;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
import org.briarproject.bramble.api.data.BdfReader;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
@@ -32,6 +34,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -39,6 +42,7 @@ import java.util.Map.Entry;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_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);
|
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
|
@Override
|
||||||
public BdfDictionary getMessageMetadataAsDictionary(MessageId m)
|
public BdfDictionary getMessageMetadataAsDictionary(MessageId m)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
@@ -389,4 +399,27 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
return tpMap;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
|
|||||||
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
|
||||||
w.getOutputStream(), ctx);
|
w.getOutputStream(), ctx);
|
||||||
ContactId c = requireNonNull(ctx.getContactId());
|
ContactId c = requireNonNull(ctx.getContactId());
|
||||||
|
// Use eager retransmission if the transport is lossy and cheap
|
||||||
return syncSessionFactory.createSimplexOutgoingSession(c,
|
return syncSessionFactory.createSimplexOutgoingSession(c,
|
||||||
ctx.getTransportId(), w.getMaxLatency(), streamWriter);
|
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
|
||||||
|
streamWriter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void close() throws DbException;
|
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.
|
* Starts a new transaction and returns an object representing it.
|
||||||
*/
|
*/
|
||||||
@@ -155,6 +162,18 @@ interface Database<T> {
|
|||||||
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
|
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are any acks or messages to send to the given
|
||||||
|
* contact over a transport with the given maximum latency.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*
|
||||||
|
* @param eager True if messages that are not yet due for retransmission
|
||||||
|
* should be included
|
||||||
|
*/
|
||||||
|
boolean containsAnythingToSend(T txn, ContactId c, int maxLatency,
|
||||||
|
boolean eager) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
* local pseudonym.
|
* local pseudonym.
|
||||||
@@ -208,6 +227,16 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
boolean containsTransport(T txn, TransportId t) throws DbException;
|
boolean containsTransport(T txn, TransportId t) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the database contains keys for communicating with the
|
||||||
|
* given contact over the given transport. Handshake mode and rotation mode
|
||||||
|
* keys are included, whether activated or not.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
boolean containsTransportKeys(T txn, ContactId c, TransportId t)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given message, the message is
|
* Returns true if the database contains the given message, the message is
|
||||||
* shared, and the visibility of the message's group to the given contact
|
* shared, and the visibility of the message's group to the given contact
|
||||||
@@ -469,11 +498,37 @@ interface Database<T> {
|
|||||||
* Returns the IDs of some messages that are eligible to be sent to the
|
* Returns the IDs of some messages that are eligible to be sent to the
|
||||||
* given contact, up to the given total length.
|
* given contact, up to the given total length.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
|
||||||
|
* does not return messages that have already been sent unless they are
|
||||||
|
* due for retransmission.
|
||||||
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
|
||||||
int maxLatency) throws DbException;
|
int maxLatency) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the IDs of all messages that are eligible to be sent to the
|
||||||
|
* given contact, together with their raw lengths.
|
||||||
|
* <p/>
|
||||||
|
* Unlike {@link #getMessagesToSend(Object, ContactId, int, int)} this
|
||||||
|
* method may return messages that have already been sent and are not yet
|
||||||
|
* due for retransmission.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total length, including headers, of all messages that are
|
||||||
|
* eligible to be sent to the given contact. This may include messages
|
||||||
|
* that have already been sent and are not yet due for retransmission.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
long getUnackedMessageBytesToSend(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that need to be validated.
|
* Returns the IDs of any messages that need to be validated.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -497,6 +552,25 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
|
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
|
* 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
|
* message is due to be sent to the given contact. The returned value may
|
||||||
@@ -554,6 +628,16 @@ interface Database<T> {
|
|||||||
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contact IDs and transport IDs for which the DB contains
|
||||||
|
* at least one set of transport keys. Handshake mode and rotation mode
|
||||||
|
* keys are included, whether activated or not.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(T txn)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given transport keys.
|
* Increments the outgoing stream counter for the given transport keys.
|
||||||
*/
|
*/
|
||||||
@@ -606,8 +690,10 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a message as having been seen by the given contact.
|
* 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.
|
* Removes a contact from the database.
|
||||||
@@ -671,6 +757,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Marks the given contact as verified.
|
||||||
*/
|
*/
|
||||||
@@ -701,9 +794,10 @@ interface Database<T> {
|
|||||||
void setMessagePermanent(T txn, MessageId m) throws DbException;
|
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.
|
* Sets the validation and delivery state of the given message.
|
||||||
@@ -730,6 +824,22 @@ interface Database<T> {
|
|||||||
void setTransportKeysActive(T txn, TransportId t, KeySetId k)
|
void setTransportKeysActive(T txn, TransportId t, KeySetId k)
|
||||||
throws DbException;
|
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
|
* Updates the transmission count, expiry time and estimated time of arrival
|
||||||
* of the given message with respect to the given contact, using the latency
|
* of the given message with respect to the given contact, using the latency
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package org.briarproject.bramble.db;
|
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.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.PendingContact;
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
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.ContactRemovedEvent;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
|
||||||
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
|
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
|
||||||
@@ -339,6 +341,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.addTransportKeys(txn, p, k);
|
return db.addTransportKeys(txn, p, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
|
||||||
|
int maxLatency, boolean eager) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.containsAnythingToSend(txn, c, maxLatency, eager);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsContact(Transaction transaction, AuthorId remote,
|
public boolean containsContact(Transaction transaction, AuthorId remote,
|
||||||
AuthorId local) throws DbException {
|
AuthorId local) throws DbException {
|
||||||
@@ -369,6 +380,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.containsPendingContact(txn, p);
|
return db.containsPendingContact(txn, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsTransportKeys(Transaction transaction, ContactId c,
|
||||||
|
TransportId t) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
return db.containsTransportKeys(txn, c, t);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteMessage(Transaction transaction, MessageId m)
|
public void deleteMessage(Transaction transaction, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -413,14 +431,43 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
db.getMessagesToSend(txn, c, maxLength, maxLatency);
|
||||||
|
long totalLength = 0;
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
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);
|
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids));
|
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Message> generateBatch(Transaction transaction,
|
||||||
|
ContactId c, Collection<MessageId> ids, int maxLatency)
|
||||||
|
throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
long totalLength = 0;
|
||||||
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
|
List<MessageId> sentIds = new ArrayList<>(ids.size());
|
||||||
|
for (MessageId m : ids) {
|
||||||
|
if (db.containsVisibleMessage(txn, c, m)) {
|
||||||
|
Message message = db.getMessage(txn, m);
|
||||||
|
totalLength += message.getRawLength();
|
||||||
|
messages.add(message);
|
||||||
|
sentIds.add(m);
|
||||||
|
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messages.isEmpty()) return messages;
|
||||||
|
db.lowerRequestedFlag(txn, c, sentIds);
|
||||||
|
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,14 +512,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
|
||||||
|
long totalLength = 0;
|
||||||
List<Message> messages = new ArrayList<>(ids.size());
|
List<Message> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
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);
|
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
|
||||||
}
|
}
|
||||||
if (ids.isEmpty()) return null;
|
if (ids.isEmpty()) return null;
|
||||||
db.lowerRequestedFlag(txn, c, ids);
|
db.lowerRequestedFlag(txn, c, ids);
|
||||||
transaction.attach(new MessagesSentEvent(c, ids));
|
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,6 +626,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessageIds(txn, g);
|
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
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -597,6 +656,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessagesToShare(txn);
|
return db.getMessagesToShare(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<GroupId, Collection<MessageId>> getMessagesToDelete(
|
||||||
|
Transaction transaction) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
return db.getMessagesToDelete(txn);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<MessageId, Metadata> getMessageMetadata(Transaction transaction,
|
public Map<MessageId, Metadata> getMessageMetadata(Transaction transaction,
|
||||||
GroupId g) throws DbException {
|
GroupId g) throws DbException {
|
||||||
@@ -674,6 +740,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<MessageId, Integer> getUnackedMessagesToSend(
|
||||||
|
Transaction transaction,
|
||||||
|
ContactId c) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.getUnackedMessagesToSend(txn, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUnackedMessageBytesToSend(Transaction transaction,
|
||||||
|
ContactId c) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.getUnackedMessageBytesToSend(txn, c);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<MessageId, MessageState> getMessageDependencies(
|
public Map<MessageId, MessageState> getMessageDependencies(
|
||||||
Transaction transaction, MessageId m) throws DbException {
|
Transaction transaction, MessageId m) throws DbException {
|
||||||
@@ -692,6 +777,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessageDependents(txn, m);
|
return db.getMessageDependents(txn, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextCleanupDeadline(Transaction transaction)
|
||||||
|
throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
return db.getNextCleanupDeadline(txn);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -740,6 +832,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getTransportKeys(txn, t);
|
return db.getTransportKeys(txn, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
|
||||||
|
Transaction transaction) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
return db.getTransportsWithKeys(txn);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||||
KeySetId k) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
@@ -795,8 +894,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
Collection<MessageId> acked = new ArrayList<>();
|
Collection<MessageId> acked = new ArrayList<>();
|
||||||
for (MessageId m : a.getMessageIds()) {
|
for (MessageId m : a.getMessageIds()) {
|
||||||
if (db.containsVisibleMessage(txn, c, m)) {
|
if (db.containsVisibleMessage(txn, c, m)) {
|
||||||
db.raiseSeenFlag(txn, c, m);
|
if (db.raiseSeenFlag(txn, c, m)) {
|
||||||
acked.add(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) {
|
if (acked.size() > 0) {
|
||||||
@@ -952,6 +1060,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.removeTransportKeys(txn, t, k);
|
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
|
@Override
|
||||||
public void setContactVerified(Transaction transaction, ContactId c)
|
public void setContactVerified(Transaction transaction, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -970,6 +1088,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
|
transaction.attach(new ContactAliasChangedEvent(c, alias));
|
||||||
db.setContactAlias(txn, c, alias);
|
db.setContactAlias(txn, c, alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,6 +1120,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.setMessagePermanent(txn, m);
|
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
|
@Override
|
||||||
public void setMessageShared(Transaction transaction, MessageId m)
|
public void setMessageShared(Transaction transaction, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -1010,7 +1139,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchMessageException();
|
throw new NoSuchMessageException();
|
||||||
if (db.getMessageState(txn, m) != DELIVERED)
|
if (db.getMessageState(txn, m) != DELIVERED)
|
||||||
throw new IllegalArgumentException("Shared undelivered message");
|
throw new IllegalArgumentException("Shared undelivered message");
|
||||||
db.setMessageShared(txn, m);
|
db.setMessageShared(txn, m, true);
|
||||||
transaction.attach(new MessageSharedEvent(m));
|
transaction.attach(new MessageSharedEvent(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1082,6 +1211,30 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.setTransportKeysActive(txn, t, k);
|
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
|
@Override
|
||||||
public void updateTransportKeys(Transaction transaction,
|
public void updateTransportKeys(Transaction transaction,
|
||||||
Collection<TransportKeySet> keys) throws DbException {
|
Collection<TransportKeySet> keys) throws DbException {
|
||||||
|
|||||||
@@ -37,4 +37,10 @@ interface DatabaseConstants {
|
|||||||
* has passed since the last compaction.
|
* has passed since the last compaction.
|
||||||
*/
|
*/
|
||||||
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
|
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
|
@Override
|
||||||
public void close() throws DbException {
|
public void close() throws DbException {
|
||||||
// H2 will close the database when the last connection closes
|
// H2 will close the database when the last connection closes
|
||||||
|
Connection c = null;
|
||||||
try {
|
try {
|
||||||
|
c = createConnection();
|
||||||
super.closeAllConnections();
|
super.closeAllConnections();
|
||||||
|
setDirty(c, false);
|
||||||
|
c.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
tryToClose(c, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
try {
|
try {
|
||||||
super.closeAllConnections();
|
super.closeAllConnections();
|
||||||
c = createConnection();
|
c = createConnection();
|
||||||
|
setDirty(c, false);
|
||||||
s = c.createStatement();
|
s = c.createStatement();
|
||||||
s.executeQuery("SHUTDOWN");
|
s.executeQuery("SHUTDOWN");
|
||||||
s.close();
|
s.close();
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -72,6 +73,8 @@ import static java.util.Arrays.asList;
|
|||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.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.db.Metadata.REMOVE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
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.SHARED;
|
||||||
@@ -81,6 +84,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.PENDING;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||||
|
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_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.MAX_COMPACTION_INTERVAL_MS;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||||
@@ -98,7 +102,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
|||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final int CODE_SCHEMA_VERSION = 47;
|
static final int CODE_SCHEMA_VERSION = 48;
|
||||||
|
|
||||||
// Time period offsets for incoming transport keys
|
// Time period offsets for incoming transport keys
|
||||||
private static final int OFFSET_PREV = -1;
|
private static final int OFFSET_PREV = -1;
|
||||||
@@ -180,6 +184,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " state INT NOT NULL,"
|
+ " state INT NOT NULL,"
|
||||||
+ " shared BOOLEAN NOT NULL,"
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
+ " temporary 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,"
|
+ " length INT NOT NULL,"
|
||||||
+ " raw BLOB," // Null if message has been deleted
|
+ " raw BLOB," // Null if message has been deleted
|
||||||
+ " PRIMARY KEY (messageId),"
|
+ " PRIMARY KEY (messageId),"
|
||||||
@@ -336,6 +345,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
||||||
+ " ON statuses (contactId, timestamp)";
|
+ " ON statuses (contactId, timestamp)";
|
||||||
|
|
||||||
|
private static final String
|
||||||
|
INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP =
|
||||||
|
"CREATE INDEX IF NOT EXISTS statusesByContactIdTxCountTimestamp"
|
||||||
|
+ " ON statuses (contactId, txCount, timestamp)";
|
||||||
|
|
||||||
|
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
|
||||||
|
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
|
||||||
|
+ " ON messages (cleanupDeadline)";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(JdbcDatabase.class.getName());
|
getLogger(JdbcDatabase.class.getName());
|
||||||
|
|
||||||
@@ -354,9 +372,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
@GuardedBy("connectionsLock")
|
@GuardedBy("connectionsLock")
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
|
|
||||||
|
private volatile boolean wasDirtyOnInitialisation = false;
|
||||||
|
|
||||||
protected abstract Connection createConnection()
|
protected abstract Connection createConnection()
|
||||||
throws DbException, SQLException;
|
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;
|
protected abstract void compactAndClose() throws DbException;
|
||||||
|
|
||||||
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||||
@@ -381,13 +404,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
if (reopen) {
|
if (reopen) {
|
||||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||||
|
wasDirtyOnInitialisation = isDirty(s);
|
||||||
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
|
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
|
||||||
} else {
|
} else {
|
||||||
|
wasDirtyOnInitialisation = false;
|
||||||
createTables(txn);
|
createTables(txn);
|
||||||
initialiseSettings(txn);
|
initialiseSettings(txn);
|
||||||
compact = false;
|
compact = false;
|
||||||
}
|
}
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("db dirty? " + wasDirtyOnInitialisation);
|
||||||
|
}
|
||||||
createIndexes(txn);
|
createIndexes(txn);
|
||||||
|
setDirty(txn, true);
|
||||||
commitTransaction(txn);
|
commitTransaction(txn);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
abortTransaction(txn);
|
abortTransaction(txn);
|
||||||
@@ -414,6 +443,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wasDirtyOnInitialisation() {
|
||||||
|
return wasDirtyOnInitialisation;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares the schema version stored in the database with the schema
|
* Compares the schema version stored in the database with the schema
|
||||||
* version used by the current code and applies any suitable migrations to
|
* version used by the current code and applies any suitable migrations to
|
||||||
@@ -463,7 +497,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
new Migration43_44(dbTypes),
|
new Migration43_44(dbTypes),
|
||||||
new Migration44_45(),
|
new Migration44_45(),
|
||||||
new Migration45_46(),
|
new Migration45_46(),
|
||||||
new Migration46_47(dbTypes)
|
new Migration46_47(dbTypes),
|
||||||
|
new Migration47_48()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,6 +523,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
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 {
|
private void initialiseSettings(Connection txn) throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
|
||||||
@@ -531,6 +576,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||||
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
|
||||||
|
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s, LOG, WARNING);
|
tryToClose(s, LOG, WARNING);
|
||||||
@@ -1080,6 +1127,55 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsAnythingToSend(Connection txn, ContactId c,
|
||||||
|
int maxLatency, boolean eager) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT NULL FROM statuses"
|
||||||
|
+ " WHERE contactId = ? AND ack = TRUE";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
boolean acksToSend = rs.next();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
if (acksToSend) return true;
|
||||||
|
if (eager) {
|
||||||
|
sql = "SELECT NULL from statuses"
|
||||||
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
|
+ " AND deleted = FALSE AND seen = FALSE";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
} else {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long eta = now + maxLatency;
|
||||||
|
sql = "SELECT NULL FROM statuses"
|
||||||
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
|
+ " AND (expiry <= ? OR eta > ?)";
|
||||||
|
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();
|
||||||
|
boolean messagesToSend = rs.next();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return messagesToSend;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsContact(Connection txn, AuthorId remote,
|
public boolean containsContact(Connection txn, AuthorId remote,
|
||||||
AuthorId local) throws DbException {
|
AuthorId local) throws DbException {
|
||||||
@@ -1237,6 +1333,29 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsTransportKeys(Connection txn, ContactId c,
|
||||||
|
TransportId t) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT NULL FROM outgoingKeys"
|
||||||
|
+ " WHERE contactId = ? AND transportId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setString(2, t.getString());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
boolean found = rs.next();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return found;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsVisibleMessage(Connection txn, ContactId c,
|
public boolean containsVisibleMessage(Connection txn, ContactId c,
|
||||||
MessageId m) throws DbException {
|
MessageId m) throws DbException {
|
||||||
@@ -1290,7 +1409,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
public void deleteMessage(Connection txn, MessageId m) throws DbException {
|
public void deleteMessage(Connection txn, MessageId m) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
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 = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getBytes());
|
ps.setBytes(1, m.getBytes());
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
@@ -1769,7 +1890,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
// Return early if there are no matches
|
// Return early if there are no matches
|
||||||
if (intersection.isEmpty()) return Collections.emptySet();
|
if (intersection.isEmpty()) return Collections.emptySet();
|
||||||
}
|
}
|
||||||
if (intersection == null) throw new AssertionError();
|
|
||||||
return intersection;
|
return intersection;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs, LOG, WARNING);
|
tryToClose(rs, LOG, WARNING);
|
||||||
@@ -2164,6 +2284,63 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
|
||||||
|
ContactId c) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT length, messageId FROM statuses"
|
||||||
|
+ " WHERE contactId = ? AND state = ?"
|
||||||
|
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||||
|
+ " AND deleted = FALSE AND seen = FALSE"
|
||||||
|
+ " ORDER BY txCount, timestamp";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
Map<MessageId, Integer> results = new LinkedHashMap<>();
|
||||||
|
while (rs.next()) {
|
||||||
|
int length = rs.getInt(1);
|
||||||
|
MessageId id = new MessageId(rs.getBytes(2));
|
||||||
|
results.put(id, length);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return results;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUnackedMessageBytesToSend(Connection txn, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
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";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
|
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
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Connection txn)
|
public Collection<MessageId> getMessagesToValidate(Connection txn)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -2226,6 +2403,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
|
@Override
|
||||||
public long getNextSendTime(Connection txn, ContactId c)
|
public long getNextSendTime(Connection txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -2256,6 +2466,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
|
@Override
|
||||||
public PendingContact getPendingContact(Connection txn, PendingContactId p)
|
public PendingContact getPendingContact(Connection txn, PendingContactId p)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -2475,6 +2710,38 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
|
||||||
|
Connection txn) throws DbException {
|
||||||
|
Statement s = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT DISTINCT contactId, transportId"
|
||||||
|
+ " FROM outgoingKeys";
|
||||||
|
s = txn.createStatement();
|
||||||
|
rs = s.executeQuery(sql);
|
||||||
|
Map<ContactId, Collection<TransportId>> ids = new HashMap<>();
|
||||||
|
while (rs.next()) {
|
||||||
|
ContactId c = new ContactId(rs.getInt(1));
|
||||||
|
TransportId t = new TransportId(rs.getString(2));
|
||||||
|
Collection<TransportId> transportIds = ids.get(c);
|
||||||
|
if (transportIds == null) {
|
||||||
|
transportIds = new ArrayList<>();
|
||||||
|
ids.put(c, transportIds);
|
||||||
|
}
|
||||||
|
transportIds.add(t);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
s.close();
|
||||||
|
return ids;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(s, LOG, WARNING);
|
||||||
|
tryToClose(s, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementStreamCounter(Connection txn, TransportId t,
|
public void incrementStreamCounter(Connection txn, TransportId t,
|
||||||
KeySetId k) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
@@ -2776,7 +3043,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void raiseSeenFlag(Connection txn, ContactId c, MessageId m)
|
public boolean raiseSeenFlag(Connection txn, ContactId c, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
@@ -2788,6 +3055,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
|
return affected == 1;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps, LOG, WARNING);
|
tryToClose(ps, LOG, WARNING);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -3021,6 +3289,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
|
@Override
|
||||||
public void setContactVerified(Connection txn, ContactId c)
|
public void setContactVerified(Connection txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -3128,22 +3415,24 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessageShared(Connection txn, MessageId m)
|
public void setMessageShared(Connection txn, MessageId m, boolean shared)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE messages SET shared = TRUE"
|
String sql = "UPDATE messages SET shared = ?"
|
||||||
+ " WHERE messageId = ?";
|
+ " WHERE messageId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getBytes());
|
ps.setBoolean(1, shared);
|
||||||
|
ps.setBytes(2, m.getBytes());
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Update denormalised column in statuses
|
// Update denormalised column in statuses
|
||||||
sql = "UPDATE statuses SET messageShared = TRUE"
|
sql = "UPDATE statuses SET messageShared = ?"
|
||||||
+ " WHERE messageId = ?";
|
+ " WHERE messageId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getBytes());
|
ps.setBoolean(1, shared);
|
||||||
|
ps.setBytes(2, m.getBytes());
|
||||||
affected = ps.executeUpdate();
|
affected = ps.executeUpdate();
|
||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -3272,6 +3561,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
|
@Override
|
||||||
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
|
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
|
||||||
int maxLatency) throws DbException {
|
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());
|
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||||
|
|
||||||
// Accept records with current protocol version, known record type
|
// 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 &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
isKnownRecordType(r.getRecordType());
|
isKnownRecordType(r.getRecordType());
|
||||||
|
|
||||||
// Ignore records with current protocol version, unknown record type
|
// 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 &&
|
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||||
!isKnownRecordType(r.getRecordType());
|
!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;
|
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.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.DuplexPlugin;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
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.Nullable;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
@NotNullByDefault
|
||||||
import static java.util.logging.Level.WARNING;
|
public interface BluetoothPlugin extends DuplexPlugin {
|
||||||
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
|
boolean isDiscovering();
|
||||||
@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;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
abstract DuplexTransportConnection discoverAndConnect(String uuid);
|
DuplexTransportConnection discoverAndConnectForSetup(String uuid);
|
||||||
|
|
||||||
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
void stopDiscoverAndConnect();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
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 boolean isLossyAndCheap() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
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.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.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ class FileTransportWriter implements TransportConnectionWriter {
|
|||||||
return plugin.getMaxIdleTime();
|
return plugin.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLossyAndCheap() {
|
||||||
|
return plugin.isLossyAndCheap();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutputStream getOutputStream() {
|
public OutputStream getOutputStream() {
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
|
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.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.Executor;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class RemovableDriveManagerImpl
|
||||||
|
implements RemovableDriveManager, RemovableDriveTaskRegistry {
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final RemovableDriveTaskFactory taskFactory;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private RemovableDriveTask reader = null;
|
||||||
|
@GuardedBy("lock")
|
||||||
|
private RemovableDriveTask writer = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
|
||||||
|
DatabaseComponent db, RemovableDriveTaskFactory taskFactory) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.db = db;
|
||||||
|
this.taskFactory = taskFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public RemovableDriveTask getCurrentReaderTask() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public RemovableDriveTask getCurrentWriterTask() {
|
||||||
|
synchronized (lock) {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemovableDriveTask startReaderTask(TransportProperties p) {
|
||||||
|
RemovableDriveTask created;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (reader != null) return reader;
|
||||||
|
reader = created = taskFactory.createReader(this, p);
|
||||||
|
}
|
||||||
|
ioExecutor.execute(created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemovableDriveTask startWriterTask(ContactId c,
|
||||||
|
TransportProperties p) {
|
||||||
|
RemovableDriveTask created;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (writer != null) return writer;
|
||||||
|
writer = created = taskFactory.createWriter(this, c, p);
|
||||||
|
}
|
||||||
|
ioExecutor.execute(created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
|
||||||
|
return db.transactionWithResult(true, txn ->
|
||||||
|
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeReader(RemovableDriveTask task) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (reader == task) reader = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWriter(RemovableDriveTask task) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (writer == task) writer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|
||||||
|
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,69 @@
|
|||||||
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
|
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.TransportConnectionReader;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private final static Logger LOG =
|
||||||
|
getLogger(RemovableDriveReaderTask.class.getName());
|
||||||
|
|
||||||
|
RemovableDriveReaderTask(
|
||||||
|
Executor eventExecutor,
|
||||||
|
PluginManager pluginManager,
|
||||||
|
ConnectionManager connectionManager,
|
||||||
|
EventBus eventBus,
|
||||||
|
RemovableDriveTaskRegistry registry,
|
||||||
|
TransportProperties transportProperties) {
|
||||||
|
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||||
|
registry, transportProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
TransportConnectionReader r =
|
||||||
|
getPlugin().createReader(transportProperties);
|
||||||
|
if (r == null) {
|
||||||
|
LOG.warning("Failed to create reader");
|
||||||
|
registry.removeReader(this);
|
||||||
|
setSuccess(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
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(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,
|
||||||
|
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,
|
||||||
|
TransportProperties p) {
|
||||||
|
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
|
||||||
|
connectionManager, eventBus, registry, 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,116 @@
|
|||||||
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Consumer;
|
||||||
|
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||||
|
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 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,
|
||||||
|
TransportProperties transportProperties) {
|
||||||
|
this.eventExecutor = eventExecutor;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.registry = registry;
|
||||||
|
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,12 @@
|
|||||||
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface RemovableDriveTaskRegistry {
|
||||||
|
|
||||||
|
void removeReader(RemovableDriveTask task);
|
||||||
|
|
||||||
|
void removeWriter(RemovableDriveTask task);
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
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;
|
||||||
|
private final ContactId contactId;
|
||||||
|
|
||||||
|
RemovableDriveWriterTask(
|
||||||
|
DatabaseComponent db,
|
||||||
|
Executor eventExecutor,
|
||||||
|
PluginManager pluginManager,
|
||||||
|
ConnectionManager connectionManager,
|
||||||
|
EventBus eventBus,
|
||||||
|
RemovableDriveTaskRegistry registry,
|
||||||
|
ContactId contactId,
|
||||||
|
TransportProperties transportProperties) {
|
||||||
|
super(eventExecutor, pluginManager, connectionManager, eventBus,
|
||||||
|
registry, transportProperties);
|
||||||
|
this.db = db;
|
||||||
|
this.contactId = contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
SimplexPlugin plugin = getPlugin();
|
||||||
|
TransportConnectionWriter w = plugin.createWriter(transportProperties);
|
||||||
|
if (w == null) {
|
||||||
|
LOG.warning("Failed to create writer");
|
||||||
|
registry.removeWriter(this);
|
||||||
|
setSuccess(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setTotal(db.transactionWithResult(true, txn ->
|
||||||
|
db.getUnackedMessageBytesToSend(txn, contactId)));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
registry.removeWriter(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 boolean isLossyAndCheap() {
|
||||||
|
return delegate.isLossyAndCheap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() throws IOException {
|
||||||
|
return delegate.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose(boolean exception) throws IOException {
|
||||||
|
delegate.dispose(exception);
|
||||||
|
registry.removeWriter(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,52 @@
|
|||||||
|
package org.briarproject.bramble.plugin.file;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
|
|
||||||
|
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 SimplexPlugin plugin;
|
||||||
|
private final OutputStream out;
|
||||||
|
|
||||||
|
TransportOutputStreamWriter(SimplexPlugin 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 boolean isLossyAndCheap() {
|
||||||
|
return plugin.isLossyAndCheap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose(boolean exception) {
|
||||||
|
tryToClose(out, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
|
|||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -115,8 +116,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||||
throws DbException, InvalidMessageException {
|
Metadata meta) throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
// Find the latest update for this transport, if any
|
// Find the latest update for this transport, if any
|
||||||
BdfDictionary d = metadataParser.parse(meta);
|
BdfDictionary d = metadataParser.parse(meta);
|
||||||
@@ -131,14 +132,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
// We've already received a newer update - delete this one
|
// We've already received a newer update - delete this one
|
||||||
db.deleteMessage(txn, m.getId());
|
db.deleteMessage(txn, m.getId());
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
return false;
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
|
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return false;
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.Versions;
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
@@ -22,7 +23,11 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
|||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@@ -61,6 +66,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final int maxLatency;
|
private final int maxLatency;
|
||||||
|
private final boolean eager;
|
||||||
private final StreamWriter streamWriter;
|
private final StreamWriter streamWriter;
|
||||||
private final SyncRecordWriter recordWriter;
|
private final SyncRecordWriter recordWriter;
|
||||||
private final AtomicInteger outstandingQueries;
|
private final AtomicInteger outstandingQueries;
|
||||||
@@ -70,7 +76,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
|
|
||||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
EventBus eventBus, ContactId contactId, TransportId transportId,
|
EventBus eventBus, ContactId contactId, TransportId transportId,
|
||||||
int maxLatency, StreamWriter streamWriter,
|
int maxLatency, boolean eager, StreamWriter streamWriter,
|
||||||
SyncRecordWriter recordWriter) {
|
SyncRecordWriter recordWriter) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
@@ -78,6 +84,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
|
this.eager = eager;
|
||||||
this.streamWriter = streamWriter;
|
this.streamWriter = streamWriter;
|
||||||
this.recordWriter = recordWriter;
|
this.recordWriter = recordWriter;
|
||||||
outstandingQueries = new AtomicInteger(2); // One per type of record
|
outstandingQueries = new AtomicInteger(2); // One per type of record
|
||||||
@@ -92,8 +99,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
// Send our supported protocol versions
|
// Send our supported protocol versions
|
||||||
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
dbExecutor.execute(new GenerateAck());
|
dbExecutor.execute(this::generateAck);
|
||||||
dbExecutor.execute(new GenerateBatch());
|
if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
|
||||||
|
else dbExecutor.execute(this::generateBatch);
|
||||||
// Write records until interrupted or no more records to write
|
// Write records until interrupted or no more records to write
|
||||||
try {
|
try {
|
||||||
while (!interrupted) {
|
while (!interrupted) {
|
||||||
@@ -138,81 +146,110 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GenerateAck implements Runnable {
|
@DatabaseExecutor
|
||||||
|
private void loadUnackedMessageIds() {
|
||||||
@DatabaseExecutor
|
if (interrupted) return;
|
||||||
@Override
|
try {
|
||||||
public void run() {
|
Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
|
||||||
if (interrupted) return;
|
db.getUnackedMessagesToSend(txn, contactId));
|
||||||
try {
|
if (LOG.isLoggable(INFO)) {
|
||||||
Ack a = db.transactionWithNullableResult(false, txn ->
|
LOG.info(ids.size() + " unacked messages to send");
|
||||||
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Generated ack: " + (a != null));
|
|
||||||
if (a == null) decrementOutstandingQueries();
|
|
||||||
else writerTasks.add(new WriteAck(a));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
}
|
||||||
|
if (ids.isEmpty()) decrementOutstandingQueries();
|
||||||
|
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WriteAck implements ThrowingRunnable<IOException> {
|
@DatabaseExecutor
|
||||||
|
private void generateEagerBatch(Map<MessageId, Integer> ids) {
|
||||||
private final Ack ack;
|
if (interrupted) return;
|
||||||
|
// Take some message IDs from `ids` to form a batch
|
||||||
private WriteAck(Ack ack) {
|
Collection<MessageId> batchIds = new ArrayList<>();
|
||||||
this.ack = ack;
|
long totalLength = 0;
|
||||||
|
Iterator<Entry<MessageId, Integer>> it = ids.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
// Check whether the next message will fit in the batch
|
||||||
|
Entry<MessageId, Integer> e = it.next();
|
||||||
|
int length = e.getValue();
|
||||||
|
if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break;
|
||||||
|
// Add the message to the batch
|
||||||
|
it.remove();
|
||||||
|
batchIds.add(e.getKey());
|
||||||
|
totalLength += length;
|
||||||
}
|
}
|
||||||
|
if (batchIds.isEmpty()) throw new AssertionError();
|
||||||
@IoExecutor
|
try {
|
||||||
@Override
|
Collection<Message> batch =
|
||||||
public void run() throws IOException {
|
db.transactionWithResult(false, txn ->
|
||||||
if (interrupted) return;
|
db.generateBatch(txn, contactId, batchIds,
|
||||||
recordWriter.writeAck(ack);
|
maxLatency));
|
||||||
LOG.info("Sent ack");
|
writerTasks.add(() -> writeEagerBatch(batch, ids));
|
||||||
dbExecutor.execute(new GenerateAck());
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GenerateBatch implements Runnable {
|
@IoExecutor
|
||||||
|
private void writeEagerBatch(Collection<Message> batch,
|
||||||
|
Map<MessageId, Integer> ids) throws IOException {
|
||||||
|
if (interrupted) return;
|
||||||
|
for (Message m : batch) recordWriter.writeMessage(m);
|
||||||
|
LOG.info("Sent eager batch");
|
||||||
|
if (ids.isEmpty()) decrementOutstandingQueries();
|
||||||
|
else dbExecutor.execute(() -> generateEagerBatch(ids));
|
||||||
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@Override
|
private void generateAck() {
|
||||||
public void run() {
|
if (interrupted) return;
|
||||||
if (interrupted) return;
|
try {
|
||||||
try {
|
Ack a = db.transactionWithNullableResult(false, txn ->
|
||||||
Collection<Message> b =
|
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
|
||||||
db.transactionWithNullableResult(false, txn ->
|
if (LOG.isLoggable(INFO))
|
||||||
db.generateBatch(txn, contactId,
|
LOG.info("Generated ack: " + (a != null));
|
||||||
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
|
if (a == null) decrementOutstandingQueries();
|
||||||
if (LOG.isLoggable(INFO))
|
else writerTasks.add(() -> writeAck(a));
|
||||||
LOG.info("Generated batch: " + (b != null));
|
} catch (DbException e) {
|
||||||
if (b == null) decrementOutstandingQueries();
|
logException(LOG, WARNING, e);
|
||||||
else writerTasks.add(new WriteBatch(b));
|
interrupt();
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
interrupt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WriteBatch implements ThrowingRunnable<IOException> {
|
@IoExecutor
|
||||||
|
private void writeAck(Ack ack) throws IOException {
|
||||||
|
if (interrupted) return;
|
||||||
|
recordWriter.writeAck(ack);
|
||||||
|
LOG.info("Sent ack");
|
||||||
|
dbExecutor.execute(this::generateAck);
|
||||||
|
}
|
||||||
|
|
||||||
private final Collection<Message> batch;
|
@DatabaseExecutor
|
||||||
|
private void generateBatch() {
|
||||||
private WriteBatch(Collection<Message> batch) {
|
if (interrupted) return;
|
||||||
this.batch = batch;
|
try {
|
||||||
|
Collection<Message> b =
|
||||||
|
db.transactionWithNullableResult(false, txn ->
|
||||||
|
db.generateBatch(txn, contactId,
|
||||||
|
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Generated batch: " + (b != null));
|
||||||
|
if (b == null) decrementOutstandingQueries();
|
||||||
|
else writerTasks.add(() -> writeBatch(b));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
interrupt();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
@Override
|
private void writeBatch(Collection<Message> batch) throws IOException {
|
||||||
public void run() throws IOException {
|
if (interrupted) return;
|
||||||
if (interrupted) return;
|
for (Message m : batch) recordWriter.writeMessage(m);
|
||||||
for (Message m : batch) recordWriter.writeMessage(m);
|
LOG.info("Sent batch");
|
||||||
LOG.info("Sent batch");
|
dbExecutor.execute(this::generateBatch);
|
||||||
dbExecutor.execute(new GenerateBatch());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
|
||||||
int maxLatency, StreamWriter streamWriter) {
|
int maxLatency, boolean eager, StreamWriter streamWriter) {
|
||||||
OutputStream out = streamWriter.getOutputStream();
|
OutputStream out = streamWriter.getOutputStream();
|
||||||
SyncRecordWriter recordWriter =
|
SyncRecordWriter recordWriter =
|
||||||
recordWriterFactory.createRecordWriter(out);
|
recordWriterFactory.createRecordWriter(out);
|
||||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
|
||||||
maxLatency, streamWriter, recordWriter);
|
maxLatency, eager, streamWriter, recordWriter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageContext;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||||
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
|
||||||
|
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
|
||||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||||
import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
import org.briarproject.bramble.api.sync.validation.MessageValidator;
|
||||||
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
@@ -40,6 +41,10 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
|
||||||
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
|
||||||
@@ -185,16 +190,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
int majorVersion = g.getMajorVersion();
|
int majorVersion = g.getMajorVersion();
|
||||||
Metadata meta =
|
Metadata meta =
|
||||||
db.getMessageMetadataForValidator(txn, id);
|
db.getMessageMetadataForValidator(txn, id);
|
||||||
DeliveryResult result =
|
DeliveryAction action =
|
||||||
deliverMessage(txn, m, c, majorVersion, meta);
|
deliverMessage(txn, m, c, majorVersion, meta);
|
||||||
if (result.valid) {
|
if (action == REJECT) {
|
||||||
addPendingDependents(txn, id, pending);
|
invalidateMessage(txn, id);
|
||||||
if (result.share) {
|
|
||||||
db.setMessageShared(txn, id);
|
|
||||||
toShare.addAll(states.keySet());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addDependentsToInvalidate(txn, id, invalidate);
|
addDependentsToInvalidate(txn, id, invalidate);
|
||||||
|
} else if (action == ACCEPT_SHARE) {
|
||||||
|
db.setMessageState(txn, m.getId(), DELIVERED);
|
||||||
|
addPendingDependents(txn, id, pending);
|
||||||
|
db.setMessageShared(txn, id);
|
||||||
|
toShare.addAll(states.keySet());
|
||||||
|
} else if (action == ACCEPT_DO_NOT_SHARE) {
|
||||||
|
db.setMessageState(txn, m.getId(), DELIVERED);
|
||||||
|
addPendingDependents(txn, id, pending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,16 +283,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
Metadata meta = context.getMetadata();
|
Metadata meta = context.getMetadata();
|
||||||
db.mergeMessageMetadata(txn, id, meta);
|
db.mergeMessageMetadata(txn, id, meta);
|
||||||
if (allDelivered) {
|
if (allDelivered) {
|
||||||
DeliveryResult result =
|
DeliveryAction action =
|
||||||
deliverMessage(txn, m, c, majorVersion, meta);
|
deliverMessage(txn, m, c, majorVersion, meta);
|
||||||
if (result.valid) {
|
if (action == REJECT) {
|
||||||
addPendingDependents(txn, id, pending);
|
invalidateMessage(txn, id);
|
||||||
if (result.share) {
|
|
||||||
db.setMessageShared(txn, id);
|
|
||||||
toShare.addAll(dependencies);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
addDependentsToInvalidate(txn, id, invalidate);
|
addDependentsToInvalidate(txn, id, invalidate);
|
||||||
|
} else if (action == DEFER) {
|
||||||
|
db.setMessageState(txn, id, PENDING);
|
||||||
|
} else if (action == ACCEPT_SHARE) {
|
||||||
|
db.setMessageState(txn, id, DELIVERED);
|
||||||
|
addPendingDependents(txn, id, pending);
|
||||||
|
db.setMessageShared(txn, id);
|
||||||
|
toShare.addAll(dependencies);
|
||||||
|
} else if (action == ACCEPT_DO_NOT_SHARE) {
|
||||||
|
db.setMessageState(txn, id, DELIVERED);
|
||||||
|
addPendingDependents(txn, id, pending);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
db.setMessageState(txn, id, PENDING);
|
db.setMessageState(txn, id, PENDING);
|
||||||
@@ -304,23 +317,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private DeliveryResult deliverMessage(Transaction txn, Message m,
|
private DeliveryAction deliverMessage(Transaction txn, Message m,
|
||||||
ClientId c, int majorVersion, Metadata meta) throws DbException {
|
ClientId c, int majorVersion, Metadata meta) {
|
||||||
// Deliver the message to the client if it's registered a hook
|
// Deliver the message to the client if it has registered a hook
|
||||||
boolean shareMsg = false;
|
|
||||||
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
||||||
IncomingMessageHook hook = hooks.get(cv);
|
IncomingMessageHook hook = hooks.get(cv);
|
||||||
if (hook != null) {
|
if (hook == null) return ACCEPT_DO_NOT_SHARE;
|
||||||
try {
|
try {
|
||||||
shareMsg = hook.incomingMessage(txn, m, meta);
|
return hook.incomingMessage(txn, m, meta);
|
||||||
} catch (InvalidMessageException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, INFO, e);
|
logException(LOG, INFO, e);
|
||||||
invalidateMessage(txn, m.getId());
|
return DEFER;
|
||||||
return new DeliveryResult(false, false);
|
} catch (InvalidMessageException e) {
|
||||||
}
|
logException(LOG, INFO, e);
|
||||||
|
return REJECT;
|
||||||
}
|
}
|
||||||
db.setMessageState(txn, m.getId(), DELIVERED);
|
|
||||||
return new DeliveryResult(true, shareMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -447,14 +458,4 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DeliveryResult {
|
|
||||||
|
|
||||||
private final boolean valid, share;
|
|
||||||
|
|
||||||
private DeliveryResult(boolean valid, boolean share) {
|
|
||||||
this.valid = valid;
|
|
||||||
this.share = share;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final PluginConfig pluginConfig;
|
private final PluginConfig pluginConfig;
|
||||||
private final TransportKeyManagerFactory transportKeyManagerFactory;
|
|
||||||
private final TransportCrypto transportCrypto;
|
private final TransportCrypto transportCrypto;
|
||||||
|
|
||||||
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
|
||||||
@@ -61,34 +60,39 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
KeyManagerImpl(DatabaseComponent db,
|
KeyManagerImpl(DatabaseComponent db,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
PluginConfig pluginConfig,
|
PluginConfig pluginConfig,
|
||||||
TransportKeyManagerFactory transportKeyManagerFactory,
|
TransportCrypto transportCrypto,
|
||||||
TransportCrypto transportCrypto) {
|
TransportKeyManagerFactory transportKeyManagerFactory) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.pluginConfig = pluginConfig;
|
this.pluginConfig = pluginConfig;
|
||||||
this.transportKeyManagerFactory = transportKeyManagerFactory;
|
|
||||||
this.transportCrypto = transportCrypto;
|
this.transportCrypto = transportCrypto;
|
||||||
managers = new ConcurrentHashMap<>();
|
managers = new ConcurrentHashMap<>();
|
||||||
|
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {
|
||||||
|
TransportKeyManager m = transportKeyManagerFactory.
|
||||||
|
createTransportKeyManager(f.getId(), f.getMaxLatency());
|
||||||
|
managers.put(f.getId(), m);
|
||||||
|
}
|
||||||
|
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) {
|
||||||
|
TransportKeyManager m = transportKeyManagerFactory.
|
||||||
|
createTransportKeyManager(f.getId(), f.getMaxLatency());
|
||||||
|
managers.put(f.getId(), m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startService() throws ServiceException {
|
public void startService() throws ServiceException {
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
Map<TransportId, Integer> transports = new HashMap<>();
|
|
||||||
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
|
|
||||||
transports.put(f.getId(), f.getMaxLatency());
|
|
||||||
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
|
|
||||||
transports.put(f.getId(), f.getMaxLatency());
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
for (Entry<TransportId, Integer> e : transports.entrySet())
|
for (SimplexPluginFactory f :
|
||||||
db.addTransport(txn, e.getKey(), e.getValue());
|
pluginConfig.getSimplexFactories()) {
|
||||||
for (Entry<TransportId, Integer> e : transports.entrySet()) {
|
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
||||||
TransportKeyManager m = transportKeyManagerFactory
|
managers.get(f.getId()).start(txn);
|
||||||
.createTransportKeyManager(e.getKey(),
|
}
|
||||||
e.getValue());
|
for (DuplexPluginFactory f :
|
||||||
managers.put(e.getKey(), m);
|
pluginConfig.getDuplexFactories()) {
|
||||||
m.start(txn);
|
db.addTransport(txn, f.getId(), f.getMaxLatency());
|
||||||
|
managers.get(f.getId()).start(txn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
@@ -101,9 +105,17 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<TransportId, KeySetId> addRotationKeys(
|
public KeySetId addRotationKeys(Transaction txn, ContactId c,
|
||||||
Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
|
TransportId t, SecretKey rootKey, long timestamp, boolean alice,
|
||||||
boolean alice, boolean active) throws DbException {
|
boolean active) throws DbException {
|
||||||
|
return withManager(t, m ->
|
||||||
|
m.addRotationKeys(txn, c, rootKey, timestamp, alice, active));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
|
||||||
|
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
|
||||||
|
boolean active) throws DbException {
|
||||||
Map<TransportId, KeySetId> ids = new HashMap<>();
|
Map<TransportId, KeySetId> ids = new HashMap<>();
|
||||||
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
||||||
TransportId t = e.getKey();
|
TransportId t = e.getKey();
|
||||||
@@ -137,7 +149,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
|
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
|
||||||
throws DbException, GeneralSecurityException {
|
throws DbException, GeneralSecurityException {
|
||||||
SecretKey staticMasterKey = transportCrypto
|
SecretKey staticMasterKey = transportCrypto
|
||||||
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
|
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
|
||||||
SecretKey rootKey =
|
SecretKey rootKey =
|
||||||
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
|
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
|
||||||
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);
|
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface MessageEncoder {
|
||||||
|
|
||||||
|
Message encodeKeyMessage(GroupId contactGroupId,
|
||||||
|
TransportId transportId, PublicKey publicKey);
|
||||||
|
|
||||||
|
Message encodeActivateMessage(GroupId contactGroupId,
|
||||||
|
TransportId transportId, MessageId previousMessageId);
|
||||||
|
|
||||||
|
BdfDictionary encodeMessageMetadata(TransportId transportId,
|
||||||
|
MessageType type, boolean local);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
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.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_LOCAL;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class MessageEncoderImpl implements MessageEncoder {
|
||||||
|
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MessageEncoderImpl(ClientHelper clientHelper, Clock clock) {
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message encodeKeyMessage(GroupId contactGroupId,
|
||||||
|
TransportId transportId, PublicKey publicKey) {
|
||||||
|
BdfList body = BdfList.of(
|
||||||
|
KEY.getValue(),
|
||||||
|
transportId.getString(),
|
||||||
|
publicKey.getEncoded());
|
||||||
|
return encodeMessage(contactGroupId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message encodeActivateMessage(GroupId contactGroupId,
|
||||||
|
TransportId transportId, MessageId previousMessageId) {
|
||||||
|
BdfList body = BdfList.of(
|
||||||
|
ACTIVATE.getValue(),
|
||||||
|
transportId.getString(),
|
||||||
|
previousMessageId);
|
||||||
|
return encodeMessage(contactGroupId, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BdfDictionary encodeMessageMetadata(TransportId transportId,
|
||||||
|
MessageType type, boolean local) {
|
||||||
|
return BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_IS_SESSION, false),
|
||||||
|
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()),
|
||||||
|
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()),
|
||||||
|
new BdfEntry(MSG_KEY_LOCAL, local));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message encodeMessage(GroupId contactGroupId, BdfList body) {
|
||||||
|
try {
|
||||||
|
return clientHelper.createMessage(contactGroupId,
|
||||||
|
clock.currentTimeMillis(), clientHelper.toByteArray(body));
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
enum MessageType {
|
||||||
|
|
||||||
|
KEY(0),
|
||||||
|
ACTIVATE(1);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
MessageType(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MessageType fromValue(int value) throws FormatException {
|
||||||
|
for (MessageType t : values()) if (t.value == value) return t;
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class Session {
|
||||||
|
|
||||||
|
private final State state;
|
||||||
|
@Nullable
|
||||||
|
private final MessageId lastLocalMessageId;
|
||||||
|
@Nullable
|
||||||
|
private final KeyPair localKeyPair;
|
||||||
|
@Nullable
|
||||||
|
private final Long localTimestamp;
|
||||||
|
@Nullable
|
||||||
|
private final KeySetId keySetId;
|
||||||
|
|
||||||
|
Session(State state, @Nullable MessageId lastLocalMessageId,
|
||||||
|
@Nullable KeyPair localKeyPair, @Nullable Long localTimestamp,
|
||||||
|
@Nullable KeySetId keySetId) {
|
||||||
|
this.state = state;
|
||||||
|
this.lastLocalMessageId = lastLocalMessageId;
|
||||||
|
this.localKeyPair = localKeyPair;
|
||||||
|
this.localTimestamp = localTimestamp;
|
||||||
|
this.keySetId = keySetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
MessageId getLastLocalMessageId() {
|
||||||
|
return lastLocalMessageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
KeyPair getLocalKeyPair() {
|
||||||
|
return localKeyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Long getLocalTimestamp() {
|
||||||
|
return localTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
KeySetId getKeySetId() {
|
||||||
|
return keySetId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface SessionEncoder {
|
||||||
|
|
||||||
|
BdfDictionary encodeSession(Session s, TransportId transportId);
|
||||||
|
|
||||||
|
BdfDictionary getSessionQuery(TransportId transportId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class SessionEncoderImpl implements SessionEncoder {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SessionEncoderImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BdfDictionary encodeSession(Session s, TransportId transportId) {
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put(MSG_KEY_IS_SESSION, true);
|
||||||
|
meta.put(MSG_KEY_TRANSPORT_ID, transportId.getString());
|
||||||
|
meta.put(SESSION_KEY_STATE, s.getState().getValue());
|
||||||
|
putNullable(meta, SESSION_KEY_LAST_LOCAL_MESSAGE_ID,
|
||||||
|
s.getLastLocalMessageId());
|
||||||
|
KeyPair localKeyPair = s.getLocalKeyPair();
|
||||||
|
if (localKeyPair == null) {
|
||||||
|
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, NULL_VALUE);
|
||||||
|
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, NULL_VALUE);
|
||||||
|
} else {
|
||||||
|
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY,
|
||||||
|
localKeyPair.getPublic().getEncoded());
|
||||||
|
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY,
|
||||||
|
localKeyPair.getPrivate().getEncoded());
|
||||||
|
}
|
||||||
|
putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
|
||||||
|
KeySetId keySetId = s.getKeySetId();
|
||||||
|
if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE);
|
||||||
|
else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt());
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BdfDictionary getSessionQuery(TransportId transportId) {
|
||||||
|
return BdfDictionary.of(
|
||||||
|
new BdfEntry(MSG_KEY_IS_SESSION, true),
|
||||||
|
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putNullable(BdfDictionary meta, String key,
|
||||||
|
@Nullable Object o) {
|
||||||
|
meta.put(key, o == null ? NULL_VALUE : o);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface SessionParser {
|
||||||
|
|
||||||
|
Session parseSession(BdfDictionary meta) throws FormatException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
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.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class SessionParserImpl implements SessionParser {
|
||||||
|
|
||||||
|
private final TransportKeyAgreementCrypto crypto;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SessionParserImpl(TransportKeyAgreementCrypto crypto) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Session parseSession(BdfDictionary meta) throws FormatException {
|
||||||
|
State state =
|
||||||
|
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
|
||||||
|
|
||||||
|
MessageId lastLocalMessageId = null;
|
||||||
|
byte[] lastLocalMessageIdBytes =
|
||||||
|
meta.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
|
||||||
|
if (lastLocalMessageIdBytes != null) {
|
||||||
|
lastLocalMessageId = new MessageId(lastLocalMessageIdBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyPair localKeyPair = null;
|
||||||
|
byte[] localPublicKeyBytes =
|
||||||
|
meta.getOptionalRaw(SESSION_KEY_LOCAL_PUBLIC_KEY);
|
||||||
|
byte[] localPrivateKeyBytes =
|
||||||
|
meta.getOptionalRaw(SESSION_KEY_LOCAL_PRIVATE_KEY);
|
||||||
|
if (localPublicKeyBytes != null && localPrivateKeyBytes != null) {
|
||||||
|
PublicKey pub = crypto.parsePublicKey(localPublicKeyBytes);
|
||||||
|
PrivateKey priv = crypto.parsePrivateKey(localPrivateKeyBytes);
|
||||||
|
localKeyPair = new KeyPair(pub, priv);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
|
||||||
|
|
||||||
|
KeySetId keySetId = null;
|
||||||
|
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
|
||||||
|
if (keySetIdLong != null) {
|
||||||
|
keySetId = new KeySetId(keySetIdLong.intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Session(state, lastLocalMessageId, localKeyPair,
|
||||||
|
localTimestamp, keySetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
enum State {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We've sent a key message and are awaiting the contact's key message.
|
||||||
|
*/
|
||||||
|
AWAIT_KEY(0),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We've exchanged key messages, derived the transport keys and sent an
|
||||||
|
* activate message, and now we're awaiting the contact's activate message.
|
||||||
|
*/
|
||||||
|
AWAIT_ACTIVATE(1),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We've exchanged key messages and activate messages, and have derived and
|
||||||
|
* activated the transport keys. This is the end state.
|
||||||
|
*/
|
||||||
|
ACTIVATED(2);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
State(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static State fromValue(int value) throws FormatException {
|
||||||
|
for (State s : values()) if (s.value == value) return s;
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface TransportKeyAgreementConstants {
|
||||||
|
|
||||||
|
String MSG_KEY_IS_SESSION = "isSession";
|
||||||
|
String MSG_KEY_MESSAGE_TYPE = "messageType";
|
||||||
|
String MSG_KEY_TRANSPORT_ID = "transportId";
|
||||||
|
String MSG_KEY_PUBLIC_KEY = "publicKey";
|
||||||
|
String MSG_KEY_LOCAL = "local";
|
||||||
|
|
||||||
|
String SESSION_KEY_STATE = "state";
|
||||||
|
String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId";
|
||||||
|
String SESSION_KEY_LOCAL_PUBLIC_KEY = "localPublicKey";
|
||||||
|
String SESSION_KEY_LOCAL_PRIVATE_KEY = "localPrivateKey";
|
||||||
|
String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp";
|
||||||
|
String SESSION_KEY_KEY_SET_ID = "keySetId";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for deriving the root key from key pairs.
|
||||||
|
*/
|
||||||
|
String ROOT_KEY_LABEL =
|
||||||
|
"org.briarproject.bramble.transport.agreement/ROOT_KEY";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface TransportKeyAgreementCrypto {
|
||||||
|
|
||||||
|
KeyPair generateKeyPair();
|
||||||
|
|
||||||
|
SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey)
|
||||||
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
|
PublicKey parsePublicKey(byte[] encoded) throws FormatException;
|
||||||
|
|
||||||
|
PrivateKey parsePrivateKey(byte[] encoded) throws FormatException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.Bytes.compare;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.ROOT_KEY_LABEL;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class TransportKeyAgreementCryptoImpl implements TransportKeyAgreementCrypto {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TransportKeyAgreementCryptoImpl(CryptoComponent crypto) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyPair generateKeyPair() {
|
||||||
|
return crypto.generateAgreementKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveRootKey(KeyPair localKeyPair,
|
||||||
|
PublicKey remotePublicKey) throws GeneralSecurityException {
|
||||||
|
byte[] theirPublic = remotePublicKey.getEncoded();
|
||||||
|
byte[] ourPublic = localKeyPair.getPublic().getEncoded();
|
||||||
|
boolean alice = compare(ourPublic, theirPublic) < 0;
|
||||||
|
byte[][] inputs = {
|
||||||
|
alice ? ourPublic : theirPublic,
|
||||||
|
alice ? theirPublic : ourPublic
|
||||||
|
};
|
||||||
|
return crypto.deriveSharedSecret(ROOT_KEY_LABEL, remotePublicKey,
|
||||||
|
localKeyPair, inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey parsePublicKey(byte[] encoded) throws FormatException {
|
||||||
|
try {
|
||||||
|
return crypto.getAgreementKeyParser().parsePublicKey(encoded);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey parsePrivateKey(byte[] encoded) throws FormatException {
|
||||||
|
try {
|
||||||
|
return crypto.getAgreementKeyParser().parsePrivateKey(encoded);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.BdfIncomingMessageHook;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.data.MetadataParser;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
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.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
|
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.Bytes.compare;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
|
||||||
|
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.State.ACTIVATED;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
|
||||||
|
implements TransportKeyAgreementManager, OpenDatabaseHook, ContactHook,
|
||||||
|
ClientVersioningHook {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(TransportKeyAgreementManagerImpl.class.getName());
|
||||||
|
|
||||||
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
|
private final ClientVersioningManager clientVersioningManager;
|
||||||
|
private final IdentityManager identityManager;
|
||||||
|
private final KeyManager keyManager;
|
||||||
|
private final MessageEncoder messageEncoder;
|
||||||
|
private final SessionEncoder sessionEncoder;
|
||||||
|
private final SessionParser sessionParser;
|
||||||
|
private final TransportKeyAgreementCrypto crypto;
|
||||||
|
|
||||||
|
private final List<TransportId> transports;
|
||||||
|
private final Group localGroup;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TransportKeyAgreementManagerImpl(
|
||||||
|
DatabaseComponent db,
|
||||||
|
ClientHelper clientHelper,
|
||||||
|
MetadataParser metadataParser,
|
||||||
|
ContactGroupFactory contactGroupFactory,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
|
IdentityManager identityManager,
|
||||||
|
KeyManager keyManager,
|
||||||
|
MessageEncoder messageEncoder,
|
||||||
|
SessionEncoder sessionEncoder,
|
||||||
|
SessionParser sessionParser,
|
||||||
|
TransportKeyAgreementCrypto crypto,
|
||||||
|
PluginConfig config) {
|
||||||
|
super(db, clientHelper, metadataParser);
|
||||||
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
|
this.identityManager = identityManager;
|
||||||
|
this.keyManager = keyManager;
|
||||||
|
this.messageEncoder = messageEncoder;
|
||||||
|
this.sessionEncoder = sessionEncoder;
|
||||||
|
this.sessionParser = sessionParser;
|
||||||
|
this.crypto = crypto;
|
||||||
|
transports = new ArrayList<>();
|
||||||
|
for (DuplexPluginFactory duplex : config.getDuplexFactories()) {
|
||||||
|
transports.add(duplex.getId());
|
||||||
|
}
|
||||||
|
for (SimplexPluginFactory simplex : config.getSimplexFactories()) {
|
||||||
|
transports.add(simplex.getId());
|
||||||
|
}
|
||||||
|
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDatabaseOpened(Transaction txn) throws DbException {
|
||||||
|
Collection<Contact> contacts = db.getContacts(txn);
|
||||||
|
if (!db.containsGroup(txn, localGroup.getId())) {
|
||||||
|
db.addGroup(txn, localGroup);
|
||||||
|
// Set things up for any pre-existing contacts
|
||||||
|
for (Contact c : contacts) addingContact(txn, c);
|
||||||
|
}
|
||||||
|
// Find any contacts and transports that need keys
|
||||||
|
Map<ContactId, Collection<TransportId>> transportsWithKeys =
|
||||||
|
db.getTransportsWithKeys(txn);
|
||||||
|
for (Contact c : contacts) {
|
||||||
|
Collection<TransportId> withKeys =
|
||||||
|
transportsWithKeys.get(c.getId());
|
||||||
|
for (TransportId t : transports) {
|
||||||
|
if (withKeys == null || !withKeys.contains(t)) {
|
||||||
|
// We need keys for this contact and transport
|
||||||
|
GroupId contactGroupId = getContactGroup(c).getId();
|
||||||
|
SavedSession ss = loadSession(txn, contactGroupId, t);
|
||||||
|
if (ss == null) {
|
||||||
|
// Start a session by sending our key message
|
||||||
|
startSession(txn, contactGroupId, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||||
|
// Create a group to share with the contact
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
db.addGroup(txn, g);
|
||||||
|
// Attach the contact ID to the group
|
||||||
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
|
// Apply the client's visibility to the contact group
|
||||||
|
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
||||||
|
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||||
|
db.removeGroup(txn, getContactGroup(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||||
|
Visibility v) throws DbException {
|
||||||
|
// Apply the client's visibility to the contact group
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||||
|
BdfList body, BdfDictionary meta)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
MessageType type = MessageType.fromValue(
|
||||||
|
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
|
||||||
|
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Received " + type + " message for " + t);
|
||||||
|
}
|
||||||
|
if (!transports.contains(t)) {
|
||||||
|
// Defer handling the message until we support the transport
|
||||||
|
return DEFER;
|
||||||
|
}
|
||||||
|
SavedSession ss = loadSession(txn, m.getGroupId(), t);
|
||||||
|
if (type == KEY) return handleKeyMessage(txn, t, m, meta, ss);
|
||||||
|
else if (type == ACTIVATE) return handleActivateMessage(txn, t, ss);
|
||||||
|
else throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeliveryAction handleKeyMessage(Transaction txn, TransportId t,
|
||||||
|
Message m, BdfDictionary meta, @Nullable SavedSession ss)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
|
||||||
|
boolean haveKeys = db.containsTransportKeys(txn, c, t);
|
||||||
|
if (ss == null) {
|
||||||
|
if (haveKeys) {
|
||||||
|
// We have keys but no session, so we must have derived keys
|
||||||
|
// when adding the contact. If the contact didn't support
|
||||||
|
// the transport when they added us, they wouldn't have
|
||||||
|
// derived keys at that time. If they later added support for
|
||||||
|
// the transport then they would have started a session, so a
|
||||||
|
// key message is valid in this case
|
||||||
|
return handleKeyMessageForNewSession(txn, c, t, m, meta);
|
||||||
|
} else {
|
||||||
|
// We don't have keys, so we should have created a session at
|
||||||
|
// startup
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
} else if (ss.session.getState() == AWAIT_KEY) {
|
||||||
|
if (haveKeys) {
|
||||||
|
// We have keys, so we shouldn't be in the AWAIT_KEY state,
|
||||||
|
// even if the contact didn't derive keys when adding us and
|
||||||
|
// later started a session
|
||||||
|
throw new IllegalStateException();
|
||||||
|
} else {
|
||||||
|
// This is the key message we're waiting for
|
||||||
|
return handleKeyMessageForExistingSession(txn, c, t, m, meta,
|
||||||
|
ss);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return REJECT; // Not valid in this state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeliveryAction handleActivateMessage(Transaction txn,
|
||||||
|
TransportId t, @Nullable SavedSession ss) throws DbException {
|
||||||
|
if (ss != null && ss.session.getState() == AWAIT_ACTIVATE) {
|
||||||
|
// Activate the keys and finish the session
|
||||||
|
KeySetId keySetId = requireNonNull(ss.session.getKeySetId());
|
||||||
|
keyManager.activateKeys(txn, singletonMap(t, keySetId));
|
||||||
|
Session session = new Session(ACTIVATED,
|
||||||
|
ss.session.getLastLocalMessageId(), null, null, null);
|
||||||
|
saveSession(txn, t, ss.storageId, session);
|
||||||
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
|
} else {
|
||||||
|
return REJECT; // Not valid in this state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeliveryAction handleKeyMessageForNewSession(Transaction txn,
|
||||||
|
ContactId c, TransportId t, Message m, BdfDictionary meta)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
KeyPair localKeyPair = crypto.generateKeyPair();
|
||||||
|
PublicKey remotePublicKey =
|
||||||
|
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
|
||||||
|
Message keyMessage = sendKeyMessage(txn, m.getGroupId(), t,
|
||||||
|
localKeyPair.getPublic());
|
||||||
|
long minTimestamp = min(keyMessage.getTimestamp(), m.getTimestamp());
|
||||||
|
SecretKey rootKey;
|
||||||
|
try {
|
||||||
|
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
return REJECT; // Invalid public key
|
||||||
|
}
|
||||||
|
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
|
||||||
|
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
|
||||||
|
minTimestamp, alice, false);
|
||||||
|
Message activateMessage =
|
||||||
|
sendActivateMessage(txn, m.getGroupId(), t, keyMessage.getId());
|
||||||
|
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
|
||||||
|
null, null, keySetId);
|
||||||
|
saveNewSession(txn, m.getGroupId(), t, session);
|
||||||
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeliveryAction handleKeyMessageForExistingSession(Transaction txn,
|
||||||
|
ContactId c, TransportId t, Message m, BdfDictionary meta,
|
||||||
|
SavedSession ss) throws DbException, FormatException {
|
||||||
|
KeyPair localKeyPair = requireNonNull(ss.session.getLocalKeyPair());
|
||||||
|
PublicKey remotePublicKey =
|
||||||
|
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
|
||||||
|
long localTimestamp = requireNonNull(ss.session.getLocalTimestamp());
|
||||||
|
long minTimestamp = min(localTimestamp, m.getTimestamp());
|
||||||
|
SecretKey rootKey;
|
||||||
|
try {
|
||||||
|
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
return REJECT; // Invalid public key
|
||||||
|
}
|
||||||
|
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
|
||||||
|
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
|
||||||
|
minTimestamp, alice, false);
|
||||||
|
MessageId previousMessageId =
|
||||||
|
requireNonNull(ss.session.getLastLocalMessageId());
|
||||||
|
Message activateMessage =
|
||||||
|
sendActivateMessage(txn, m.getGroupId(), t, previousMessageId);
|
||||||
|
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
|
||||||
|
null, null, keySetId);
|
||||||
|
saveSession(txn, t, ss.storageId, session);
|
||||||
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startSession(Transaction txn, GroupId contactGroupId,
|
||||||
|
TransportId t) throws DbException {
|
||||||
|
KeyPair localKeyPair = crypto.generateKeyPair();
|
||||||
|
Message keyMessage = sendKeyMessage(txn, contactGroupId, t,
|
||||||
|
localKeyPair.getPublic());
|
||||||
|
Session session = new Session(AWAIT_KEY, keyMessage.getId(),
|
||||||
|
localKeyPair, keyMessage.getTimestamp(), null);
|
||||||
|
saveNewSession(txn, contactGroupId, t, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private SavedSession loadSession(Transaction txn, GroupId contactGroupId,
|
||||||
|
TransportId t) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfDictionary query = sessionEncoder.getSessionQuery(t);
|
||||||
|
Collection<MessageId> ids =
|
||||||
|
clientHelper.getMessageIds(txn, contactGroupId, query);
|
||||||
|
if (ids.size() > 1) throw new DbException();
|
||||||
|
if (ids.isEmpty()) {
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info("No session for " + t);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
MessageId storageId = ids.iterator().next();
|
||||||
|
BdfDictionary bdfSession =
|
||||||
|
clientHelper.getMessageMetadataAsDictionary(txn, storageId);
|
||||||
|
Session session = sessionParser.parseSession(bdfSession);
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Loaded session in state " + session.getState()
|
||||||
|
+ " for " + t);
|
||||||
|
}
|
||||||
|
return new SavedSession(session, storageId);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveNewSession(Transaction txn, GroupId contactGroupId,
|
||||||
|
TransportId t, Session session) throws DbException {
|
||||||
|
Message m =
|
||||||
|
clientHelper.createMessageForStoringMetadata(contactGroupId);
|
||||||
|
db.addLocalMessage(txn, m, new Metadata(), false, false);
|
||||||
|
MessageId storageId = m.getId();
|
||||||
|
saveSession(txn, t, storageId, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveSession(Transaction txn, TransportId t,
|
||||||
|
MessageId storageId, Session session) throws DbException {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Saving session in state " + session.getState()
|
||||||
|
+ " for " + t);
|
||||||
|
}
|
||||||
|
BdfDictionary meta = sessionEncoder.encodeSession(session, t);
|
||||||
|
try {
|
||||||
|
clientHelper.mergeMessageMetadata(txn, storageId, meta);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message sendKeyMessage(Transaction txn, GroupId contactGroupId,
|
||||||
|
TransportId t, PublicKey publicKey) throws DbException {
|
||||||
|
Message m = messageEncoder.encodeKeyMessage(contactGroupId, t,
|
||||||
|
publicKey);
|
||||||
|
sendMessage(txn, t, m, KEY);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message sendActivateMessage(Transaction txn,
|
||||||
|
GroupId contactGroupId, TransportId t, MessageId previousMessageId)
|
||||||
|
throws DbException {
|
||||||
|
Message m = messageEncoder.encodeActivateMessage(contactGroupId, t,
|
||||||
|
previousMessageId);
|
||||||
|
sendMessage(txn, t, m, ACTIVATE);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessage(Transaction txn, TransportId t, Message m,
|
||||||
|
MessageType type) throws DbException {
|
||||||
|
BdfDictionary meta =
|
||||||
|
messageEncoder.encodeMessageMetadata(t, type, true);
|
||||||
|
try {
|
||||||
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Group getContactGroup(Contact c) {
|
||||||
|
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocalPartyAlice(Transaction txn, Contact c)
|
||||||
|
throws DbException {
|
||||||
|
Author local = identityManager.getLocalAuthor(txn);
|
||||||
|
Author remote = c.getAuthor();
|
||||||
|
return compare(local.getId().getBytes(), remote.getId().getBytes()) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SavedSession {
|
||||||
|
|
||||||
|
private final Session session;
|
||||||
|
private final MessageId storageId;
|
||||||
|
|
||||||
|
private SavedSession(Session session, MessageId storageId) {
|
||||||
|
this.session = session;
|
||||||
|
this.storageId = storageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.sync.validation.ValidationManager;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID;
|
||||||
|
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MINOR_VERSION;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class TransportKeyAgreementModule {
|
||||||
|
|
||||||
|
public static class EagerSingletons {
|
||||||
|
@Inject
|
||||||
|
TransportKeyAgreementManager transportKeyAgreementManager;
|
||||||
|
@Inject
|
||||||
|
TransportKeyAgreementValidator transportKeyAgreementValidator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
TransportKeyAgreementManager provideTransportKeyAgreementManager(
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
|
ValidationManager validationManager,
|
||||||
|
ContactManager contactManager,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
|
TransportKeyAgreementManagerImpl transportKeyAgreementManager) {
|
||||||
|
lifecycleManager.registerOpenDatabaseHook(transportKeyAgreementManager);
|
||||||
|
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, transportKeyAgreementManager);
|
||||||
|
contactManager.registerContactHook(transportKeyAgreementManager);
|
||||||
|
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||||
|
MINOR_VERSION, transportKeyAgreementManager);
|
||||||
|
return transportKeyAgreementManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
TransportKeyAgreementValidator provideTransportKeyAgreementValidator(
|
||||||
|
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
|
||||||
|
Clock clock, MessageEncoder messageEncoder,
|
||||||
|
ValidationManager validationManager) {
|
||||||
|
TransportKeyAgreementValidator validator =
|
||||||
|
new TransportKeyAgreementValidator(clientHelper,
|
||||||
|
metadataEncoder, clock, messageEncoder);
|
||||||
|
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||||
|
validator);
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
|
||||||
|
return messageEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
|
||||||
|
return sessionEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
|
||||||
|
return sessionParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
TransportKeyAgreementCrypto provideTransportKeyAgreementCrypto(
|
||||||
|
TransportKeyAgreementCryptoImpl transportKeyAgreementCrypto) {
|
||||||
|
return transportKeyAgreementCrypto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package org.briarproject.bramble.transport.agreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||||
|
import org.briarproject.bramble.api.client.BdfMessageValidator;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||||
|
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
|
||||||
|
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
|
||||||
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class TransportKeyAgreementValidator extends BdfMessageValidator {
|
||||||
|
|
||||||
|
private final MessageEncoder messageEncoder;
|
||||||
|
|
||||||
|
TransportKeyAgreementValidator(ClientHelper clientHelper,
|
||||||
|
MetadataEncoder metadataEncoder, Clock clock,
|
||||||
|
MessageEncoder messageEncoder) {
|
||||||
|
super(clientHelper, metadataEncoder, clock);
|
||||||
|
this.messageEncoder = messageEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||||
|
BdfList body) throws FormatException {
|
||||||
|
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
|
||||||
|
if (type == KEY) return validateKeyMessage(body);
|
||||||
|
else if (type == ACTIVATE) return validateActivateMessage(body);
|
||||||
|
else throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfMessageContext validateKeyMessage(BdfList body)
|
||||||
|
throws FormatException {
|
||||||
|
// Message type, transport ID, public key
|
||||||
|
checkSize(body, 3);
|
||||||
|
String transportId = body.getString(1);
|
||||||
|
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||||
|
byte[] publicKey = body.getRaw(2);
|
||||||
|
checkLength(publicKey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||||
|
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
|
||||||
|
new TransportId(transportId), KEY, false);
|
||||||
|
meta.put(MSG_KEY_PUBLIC_KEY, publicKey);
|
||||||
|
return new BdfMessageContext(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfMessageContext validateActivateMessage(BdfList body)
|
||||||
|
throws FormatException {
|
||||||
|
// Message type, transport ID, previous message ID
|
||||||
|
checkSize(body, 3);
|
||||||
|
String transportId = body.getString(1);
|
||||||
|
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
|
||||||
|
byte[] previousMessageId = body.getRaw(2);
|
||||||
|
checkLength(previousMessageId, MessageId.LENGTH);
|
||||||
|
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
|
||||||
|
new TransportId(transportId), ACTIVATE, false);
|
||||||
|
MessageId dependency = new MessageId(previousMessageId);
|
||||||
|
return new BdfMessageContext(meta, singletonList(dependency));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,5 @@ interface ClientVersioningConstants {
|
|||||||
// Metadata keys
|
// Metadata keys
|
||||||
String MSG_KEY_UPDATE_VERSION = "version";
|
String MSG_KEY_UPDATE_VERSION = "version";
|
||||||
String MSG_KEY_LOCAL = "local";
|
String MSG_KEY_LOCAL = "local";
|
||||||
String GROUP_KEY_CONTACT_ID = "contactId";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ 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.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
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.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
|
|
||||||
@@ -161,13 +161,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
db.addGroup(txn, g);
|
db.addGroup(txn, g);
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||||
// Attach the contact ID to the group
|
// Attach the contact ID to the group
|
||||||
BdfDictionary meta = new BdfDictionary();
|
clientHelper.setContactId(txn, g.getId(), c.getId());
|
||||||
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
|
||||||
try {
|
|
||||||
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
// Create and store the first local update
|
// Create and store the first local update
|
||||||
List<ClientVersion> versions = new ArrayList<>(clients);
|
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||||
Collections.sort(versions);
|
Collections.sort(versions);
|
||||||
@@ -180,8 +174,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
public DeliveryAction incomingMessage(Transaction txn, Message m,
|
||||||
throws DbException, InvalidMessageException {
|
Metadata meta) throws DbException, InvalidMessageException {
|
||||||
try {
|
try {
|
||||||
// Parse the new remote update
|
// Parse the new remote update
|
||||||
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
||||||
@@ -194,7 +188,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
||||||
db.deleteMessage(txn, m.getId());
|
db.deleteMessage(txn, m.getId());
|
||||||
db.deleteMessageMetadata(txn, m.getId());
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
return false;
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
}
|
}
|
||||||
// Load and parse the latest local update
|
// Load and parse the latest local update
|
||||||
if (latest.local == null) throw new DbException();
|
if (latest.local == null) throw new DbException();
|
||||||
@@ -229,7 +223,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
Map<ClientMajorVersion, Visibility> after =
|
Map<ClientMajorVersion, Visibility> after =
|
||||||
getVisibilities(newLocalStates, newRemoteStates);
|
getVisibilities(newLocalStates, newRemoteStates);
|
||||||
// Call hooks for any visibilities that have changed
|
// 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)) {
|
if (!before.equals(after)) {
|
||||||
Contact contact = db.getContact(txn, c);
|
Contact contact = db.getContact(txn, c);
|
||||||
callVisibilityHooks(txn, contact, before, after);
|
callVisibilityHooks(txn, contact, before, after);
|
||||||
@@ -248,7 +242,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
return false;
|
return ACCEPT_DO_NOT_SHARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeClientVersions(Transaction txn,
|
private void storeClientVersions(Transaction txn,
|
||||||
@@ -521,17 +515,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
storeUpdate(txn, g, states, 1);
|
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(
|
private List<ClientState> updateStatesFromRemoteStates(
|
||||||
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
|
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
|
||||||
Set<ClientMajorVersion> remoteSet = new HashSet<>();
|
Set<ClientMajorVersion> remoteSet = new HashSet<>();
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
|
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.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 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 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw 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: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:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||||
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
|
||||||
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||||
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||||
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
package org.briarproject.bramble;
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
|
import org.briarproject.bramble.system.TimeTravelModule;
|
||||||
|
|
||||||
public interface BrambleCoreIntegrationTestEagerSingletons
|
public interface BrambleCoreIntegrationTestEagerSingletons
|
||||||
extends BrambleCoreEagerSingletons {
|
extends BrambleCoreEagerSingletons {
|
||||||
|
|
||||||
void inject(DefaultTaskSchedulerModule.EagerSingletons init);
|
void inject(TimeTravelModule.EagerSingletons init);
|
||||||
|
|
||||||
class Helper {
|
class Helper {
|
||||||
|
|
||||||
public static void injectEagerSingletons(
|
public static void injectEagerSingletons(
|
||||||
BrambleCoreIntegrationTestEagerSingletons c) {
|
BrambleCoreIntegrationTestEagerSingletons c) {
|
||||||
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
|
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
|
||||||
c.inject(new DefaultTaskSchedulerModule.EagerSingletons());
|
c.inject(new TimeTravelModule.EagerSingletons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.db;
|
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.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
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.emptyList;
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.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.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
@@ -295,11 +298,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// 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));
|
will(returnValue(txn));
|
||||||
exactly(18).of(database).containsContact(txn, contactId);
|
exactly(19).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(18).of(database).abortTransaction(txn);
|
exactly(19).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
@@ -346,7 +349,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getContact(transaction, contactId));
|
db.getContact(transaction, contactId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -354,7 +357,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
|
db.getUnackedMessageBytesToSend(transaction, contactId));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(true, transaction ->
|
||||||
db.getMessageStatus(transaction, contactId, groupId));
|
db.getMessageStatus(transaction, contactId, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -362,7 +373,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageStatus(transaction, contactId, messageId));
|
db.getMessageStatus(transaction, contactId, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -370,7 +381,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getGroupVisibility(transaction, contactId, groupId));
|
db.getGroupVisibility(transaction, contactId, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -378,7 +389,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getSyncVersions(transaction, contactId));
|
db.getSyncVersions(transaction, contactId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
@@ -510,11 +521,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the group is in the DB (which it's not)
|
// 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));
|
will(returnValue(txn));
|
||||||
exactly(8).of(database).containsGroup(txn, groupId);
|
exactly(10).of(database).containsGroup(txn, groupId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(8).of(database).abortTransaction(txn);
|
exactly(10).of(database).abortTransaction(txn);
|
||||||
// Allow other checks to pass
|
// Allow other checks to pass
|
||||||
allowing(database).containsContact(txn, contactId);
|
allowing(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
@@ -523,7 +534,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getGroup(transaction, groupId));
|
db.getGroup(transaction, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchGroupException expected) {
|
} catch (NoSuchGroupException expected) {
|
||||||
@@ -531,7 +542,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getGroupMetadata(transaction, groupId));
|
db.getGroupMetadata(transaction, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchGroupException expected) {
|
} catch (NoSuchGroupException expected) {
|
||||||
@@ -539,7 +550,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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));
|
db.getMessageMetadata(transaction, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchGroupException expected) {
|
} catch (NoSuchGroupException expected) {
|
||||||
@@ -547,7 +574,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageMetadata(transaction, groupId,
|
db.getMessageMetadata(transaction, groupId,
|
||||||
new Metadata()));
|
new Metadata()));
|
||||||
fail();
|
fail();
|
||||||
@@ -556,7 +583,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageStatus(transaction, contactId, groupId));
|
db.getMessageStatus(transaction, contactId, groupId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchGroupException expected) {
|
} catch (NoSuchGroupException expected) {
|
||||||
@@ -594,11 +621,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the message is in the DB (which it's not)
|
// Check whether the message is in the DB (which it's not)
|
||||||
exactly(12).of(database).startTransaction();
|
exactly(15).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(12).of(database).containsMessage(txn, messageId);
|
exactly(15).of(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(12).of(database).abortTransaction(txn);
|
exactly(15).of(database).abortTransaction(txn);
|
||||||
// Allow other checks to pass
|
// Allow other checks to pass
|
||||||
allowing(database).containsContact(txn, contactId);
|
allowing(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
@@ -623,7 +650,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessage(transaction, messageId));
|
db.getMessage(transaction, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchMessageException expected) {
|
} catch (NoSuchMessageException expected) {
|
||||||
@@ -631,7 +658,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageMetadata(transaction, messageId));
|
db.getMessageMetadata(transaction, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchMessageException expected) {
|
} catch (NoSuchMessageException expected) {
|
||||||
@@ -639,7 +666,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageState(transaction, messageId));
|
db.getMessageState(transaction, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchMessageException expected) {
|
} catch (NoSuchMessageException expected) {
|
||||||
@@ -647,7 +674,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageStatus(transaction, contactId, messageId));
|
db.getMessageStatus(transaction, contactId, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchMessageException expected) {
|
} catch (NoSuchMessageException expected) {
|
||||||
@@ -662,6 +689,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.setCleanupTimerDuration(transaction, message.getId(),
|
||||||
|
HOURS.toMillis(1)));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchMessageException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.setMessagePermanent(transaction, message.getId()));
|
db.setMessagePermanent(transaction, message.getId()));
|
||||||
@@ -687,7 +723,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageDependencies(transaction, messageId));
|
db.getMessageDependencies(transaction, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchMessageException expected) {
|
} catch (NoSuchMessageException expected) {
|
||||||
@@ -695,12 +731,28 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(true, transaction ->
|
||||||
db.getMessageDependents(transaction, messageId));
|
db.getMessageDependents(transaction, messageId));
|
||||||
fail();
|
fail();
|
||||||
} catch (NoSuchMessageException expected) {
|
} catch (NoSuchMessageException expected) {
|
||||||
// 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
|
@Test
|
||||||
@@ -981,6 +1033,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
|
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(database).commitTransaction(txn);
|
||||||
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
|
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
|
@Test
|
||||||
public void testReceiveMessage() throws Exception {
|
public void testReceiveMessage() throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
|
|||||||
@@ -41,8 +41,12 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
import java.sql.Driver;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -53,10 +57,12 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.singleton;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
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.db.Metadata.REMOVE;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
@@ -217,18 +223,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The contact has not seen the message, so it should be sendable
|
// The contact has not seen the message, so it should be sendable
|
||||||
Collection<MessageId> ids =
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertOneMessageToSendLazily(db, txn);
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
|
|
||||||
// Changing the status to seen = true should make the message unsendable
|
// Changing the status to seen = true should make the message unsendable
|
||||||
db.raiseSeenFlag(txn, contactId, messageId);
|
db.raiseSeenFlag(txn, contactId, messageId);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendEagerly(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendLazily(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -248,32 +249,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, UNKNOWN, true, false, null);
|
db.addMessage(txn, message, UNKNOWN, true, false, null);
|
||||||
|
|
||||||
// The message has not been validated, so it should not be sendable
|
// The message has not been validated, so it should not be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
assertNothingToSendLazily(db, txn);
|
||||||
ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendEagerly(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
// Marking the message delivered should make it sendable
|
// Marking the message delivered should make it sendable
|
||||||
db.setMessageState(txn, messageId, DELIVERED);
|
db.setMessageState(txn, messageId, DELIVERED);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertOneMessageToSendLazily(db, txn);
|
||||||
assertEquals(singletonList(messageId), ids);
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
|
|
||||||
// Marking the message invalid should make it unsendable
|
// Marking the message invalid should make it unsendable
|
||||||
db.setMessageState(txn, messageId, INVALID);
|
db.setMessageState(txn, messageId, INVALID);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendLazily(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
// Marking the message pending should make it unsendable
|
// Marking the message pending should make it unsendable
|
||||||
db.setMessageState(txn, messageId, PENDING);
|
db.setMessageState(txn, messageId, PENDING);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendLazily(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -292,39 +284,28 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The group is invisible, so the message should not be sendable
|
// The group is invisible, so the message should not be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
assertNothingToSendLazily(db, txn);
|
||||||
ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendEagerly(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
// Making the group visible should not make the message sendable
|
// Making the group visible should not make the message sendable
|
||||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendLazily(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
// Sharing the group should make the message sendable
|
// Sharing the group should make the message sendable
|
||||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
assertEquals(singletonList(messageId), ids);
|
assertOneMessageToSendLazily(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
|
|
||||||
// Unsharing the group should make the message unsendable
|
// Unsharing the group should make the message unsendable
|
||||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendLazily(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
// Making the group invisible should make the message unsendable
|
// Making the group invisible should make the message unsendable
|
||||||
db.removeGroupVisibility(txn, contactId, groupId);
|
db.removeGroupVisibility(txn, contactId, groupId);
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendLazily(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -344,18 +325,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, false, false, null);
|
db.addMessage(txn, message, DELIVERED, false, false, null);
|
||||||
|
|
||||||
// The message is not shared, so it should not be sendable
|
// The message is not shared, so it should not be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
assertNothingToSendLazily(db, txn);
|
||||||
ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendEagerly(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
// Sharing the message should make it sendable
|
// 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);
|
assertOneMessageToSendLazily(db, txn);
|
||||||
assertEquals(singletonList(messageId), ids);
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -375,10 +351,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The message is sendable, but too large to send
|
// The message is sendable, but too large to send
|
||||||
|
assertOneMessageToSendLazily(db, txn);
|
||||||
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
|
||||||
MAX_LATENCY);
|
MAX_LATENCY);
|
||||||
assertTrue(ids.isEmpty());
|
assertTrue(ids.isEmpty());
|
||||||
|
|
||||||
// The message is just the right size to send
|
// The message is just the right size to send
|
||||||
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
|
||||||
MAX_LATENCY);
|
MAX_LATENCY);
|
||||||
@@ -400,6 +379,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||||
|
|
||||||
|
// Initially there should be nothing to send
|
||||||
|
assertFalse(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
|
assertFalse(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
|
|
||||||
// Add some messages to ack
|
// Add some messages to ack
|
||||||
Message message1 = getMessage(groupId);
|
Message message1 = getMessage(groupId);
|
||||||
MessageId messageId1 = message1.getId();
|
MessageId messageId1 = message1.getId();
|
||||||
@@ -407,6 +392,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
|
assertTrue(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
|
assertTrue(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
@@ -414,6 +403,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||||
|
|
||||||
// Both message IDs should have been removed
|
// Both message IDs should have been removed
|
||||||
|
assertFalse(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
|
assertFalse(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
||||||
contactId, 1234));
|
contactId, 1234));
|
||||||
|
|
||||||
@@ -422,6 +415,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.raiseAckFlag(txn, contactId, messageId1);
|
db.raiseAckFlag(txn, contactId, messageId1);
|
||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
|
assertTrue(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
|
assertTrue(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
@@ -442,22 +439,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, false, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Retrieve the message from the database and mark it as sent
|
// The message should be sendable via lazy or eager retransmission
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
assertOneMessageToSendLazily(db, txn);
|
||||||
ONE_MEGABYTE, MAX_LATENCY);
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
|
// Mark the message as sent
|
||||||
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
|
||||||
|
|
||||||
// The message should no longer be sendable
|
// The message should no longer be sendable via lazy retransmission,
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
// but it should still be sendable via eager retransmission
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendLazily(db, txn);
|
||||||
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
|
|
||||||
// Pretend that the message was acked
|
// Mark the message as acked
|
||||||
db.raiseSeenFlag(txn, contactId, messageId);
|
db.raiseSeenFlag(txn, contactId, messageId);
|
||||||
|
|
||||||
// The message still should not be sendable
|
// The message still should not be sendable via lazy or eager
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
// retransmission
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendLazily(db, txn);
|
||||||
|
assertNothingToSendEagerly(db, txn);
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -631,8 +631,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// The group should not be visible to the contact
|
// The group should not be visible to the contact
|
||||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||||
assertEquals(emptyMap(),
|
assertTrue(db.getGroupVisibility(txn, groupId).isEmpty());
|
||||||
db.getGroupVisibility(txn, groupId));
|
|
||||||
|
|
||||||
// Make the group visible to the contact
|
// Make the group visible to the contact
|
||||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||||
@@ -655,8 +654,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Make the group invisible again
|
// Make the group invisible again
|
||||||
db.removeGroupVisibility(txn, contactId, groupId);
|
db.removeGroupVisibility(txn, contactId, groupId);
|
||||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||||
assertEquals(emptyMap(),
|
assertTrue(db.getGroupVisibility(txn, groupId).isEmpty());
|
||||||
db.getGroupVisibility(txn, groupId));
|
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -673,7 +671,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
Connection txn = db.startTransaction();
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
// Initially there should be no transport keys in the database
|
// Initially there should be no transport keys in the database
|
||||||
|
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
|
||||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
|
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
|
||||||
|
|
||||||
// Add the contact, the transport and the transport keys
|
// Add the contact, the transport and the transport keys
|
||||||
db.addIdentity(txn, identity);
|
db.addIdentity(txn, identity);
|
||||||
@@ -684,6 +684,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
||||||
|
|
||||||
// Retrieve the transport keys
|
// Retrieve the transport keys
|
||||||
|
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
|
||||||
Collection<TransportKeySet> allKeys =
|
Collection<TransportKeySet> allKeys =
|
||||||
db.getTransportKeys(txn, transportId);
|
db.getTransportKeys(txn, transportId);
|
||||||
assertEquals(2, allKeys.size());
|
assertEquals(2, allKeys.size());
|
||||||
@@ -696,6 +697,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertKeysEquals(keys1, ks.getKeys());
|
assertKeysEquals(keys1, ks.getKeys());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assertEquals(singletonMap(contactId, singletonList(transportId)),
|
||||||
|
db.getTransportsWithKeys(txn));
|
||||||
|
|
||||||
// Update the transport keys
|
// Update the transport keys
|
||||||
TransportKeys updated = createTransportKeys(timePeriod + 1, active);
|
TransportKeys updated = createTransportKeys(timePeriod + 1, active);
|
||||||
@@ -707,6 +710,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
null, updated1));
|
null, updated1));
|
||||||
|
|
||||||
// Retrieve the transport keys again
|
// Retrieve the transport keys again
|
||||||
|
assertTrue(db.containsTransportKeys(txn, contactId, transportId));
|
||||||
allKeys = db.getTransportKeys(txn, transportId);
|
allKeys = db.getTransportKeys(txn, transportId);
|
||||||
assertEquals(2, allKeys.size());
|
assertEquals(2, allKeys.size());
|
||||||
for (TransportKeySet ks : allKeys) {
|
for (TransportKeySet ks : allKeys) {
|
||||||
@@ -718,10 +722,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertKeysEquals(updated1, ks.getKeys());
|
assertKeysEquals(updated1, ks.getKeys());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assertEquals(singletonMap(contactId, singletonList(transportId)),
|
||||||
|
db.getTransportsWithKeys(txn));
|
||||||
|
|
||||||
// Removing the contact should remove the transport keys
|
// Removing the contact should remove the transport keys
|
||||||
db.removeContact(txn, contactId);
|
db.removeContact(txn, contactId);
|
||||||
|
assertFalse(db.containsTransportKeys(txn, contactId, transportId));
|
||||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||||
|
assertTrue(db.getTransportsWithKeys(txn).isEmpty());
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -1922,11 +1930,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
|
|
||||||
// The message should be sendable
|
// The message should be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
assertOneMessageToSendLazily(db, txn);
|
||||||
ONE_MEGABYTE, MAX_LATENCY);
|
assertOneMessageToSendEagerly(db, txn);
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertEquals(singletonList(messageId), ids);
|
|
||||||
|
|
||||||
// The message should be available
|
// The message should be available
|
||||||
Message m = db.getMessage(txn, messageId);
|
Message m = db.getMessage(txn, messageId);
|
||||||
@@ -1942,10 +1947,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
|
|
||||||
// The message should not be sendable
|
// The message should not be sendable
|
||||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
assertNothingToSendLazily(db, txn);
|
||||||
assertTrue(ids.isEmpty());
|
assertNothingToSendEagerly(db, txn);
|
||||||
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
|
||||||
assertTrue(ids.isEmpty());
|
|
||||||
|
|
||||||
// Requesting the message should throw an exception
|
// Requesting the message should throw an exception
|
||||||
try {
|
try {
|
||||||
@@ -2040,7 +2043,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
// Share the message - now it should be sendable immediately
|
// Share the message - now it should be sendable immediately
|
||||||
db.setMessageShared(txn, messageId);
|
db.setMessageShared(txn, messageId, true);
|
||||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||||
|
|
||||||
// Mark the message as requested - it should still be sendable
|
// Mark the message as requested - it should still be sendable
|
||||||
@@ -2347,6 +2350,148 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.close();
|
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 {
|
private Database<Connection> open(boolean resume) throws Exception {
|
||||||
return open(resume, new TestMessageFactory(), new SystemClock());
|
return open(resume, new TestMessageFactory(), new SystemClock());
|
||||||
}
|
}
|
||||||
@@ -2402,11 +2547,80 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
rootKey, alice);
|
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
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
deleteTestDirectory(testDir);
|
deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertNothingToSendLazily(Database<Connection> db,
|
||||||
|
Connection txn) throws Exception {
|
||||||
|
assertFalse(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
|
Collection<MessageId> ids =
|
||||||
|
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
ids = db.getMessagesToOffer(txn, contactId, 100, MAX_LATENCY);
|
||||||
|
assertTrue(ids.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOneMessageToSendLazily(Database<Connection> db,
|
||||||
|
Connection txn) throws Exception {
|
||||||
|
assertTrue(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
|
||||||
|
Collection<MessageId> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNothingToSendEagerly(Database<Connection> db,
|
||||||
|
Connection txn) throws Exception {
|
||||||
|
assertFalse(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
|
Map<MessageId, Integer> unacked =
|
||||||
|
db.getUnackedMessagesToSend(txn, contactId);
|
||||||
|
assertTrue(unacked.isEmpty());
|
||||||
|
assertEquals(0, db.getUnackedMessageBytesToSend(txn, contactId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOneMessageToSendEagerly(Database<Connection> db,
|
||||||
|
Connection txn) throws Exception {
|
||||||
|
assertTrue(
|
||||||
|
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
|
||||||
|
Map<MessageId, Integer> unacked =
|
||||||
|
db.getUnackedMessagesToSend(txn, contactId);
|
||||||
|
assertEquals(singleton(messageId), unacked.keySet());
|
||||||
|
assertEquals(message.getRawLength(), unacked.get(messageId).intValue());
|
||||||
|
assertEquals(message.getRawLength(),
|
||||||
|
db.getUnackedMessageBytesToSend(txn, contactId));
|
||||||
|
}
|
||||||
|
|
||||||
private static class StoppedClock implements Clock {
|
private static class StoppedClock implements Clock {
|
||||||
|
|
||||||
private final long time;
|
private final long time;
|
||||||
|
|||||||
@@ -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, write(alice, bobId), 2);
|
||||||
|
// Sync Bob's client versions and transport properties
|
||||||
|
read(alice, 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,
|
||||||
|
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(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.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.PluginConfig;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
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.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class DesktopPluginModule extends PluginModule {
|
class RemovableDriveIntegrationTestModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
PluginConfig getPluginConfig(JavaBluetoothPluginFactory bluetooth,
|
@Singleton
|
||||||
ModemPluginFactory modem, LanTcpPluginFactory lan,
|
PluginConfig providePluginConfig(RemovableDrivePluginFactory drive) {
|
||||||
WanTcpPluginFactory wan) {
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
PluginConfig pluginConfig = new PluginConfig() {
|
PluginConfig pluginConfig = new PluginConfig() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||||
return asList(bluetooth, modem, lan, wan);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||||
|
return singletonList(drive);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldPoll() {
|
public boolean shouldPoll() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<TransportId, List<TransportId>> getTransportPreferences() {
|
public Map<TransportId, List<TransportId>> getTransportPreferences() {
|
||||||
// Prefer LAN to Bluetooth
|
return emptyMap();
|
||||||
return singletonMap(BluetoothConstants.ID,
|
|
||||||
singletonList(LanTcpConstants.ID));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
return pluginConfig;
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user