mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Compare commits
440 Commits
beta-2017-
...
beta-0.16.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
812522a900 | ||
|
|
98db9da4bc | ||
|
|
eda3c964aa | ||
|
|
68df606146 | ||
|
|
52bd699d2d | ||
|
|
abb8db10db | ||
|
|
30edb90426 | ||
|
|
ffc94b2812 | ||
|
|
35a7bb4576 | ||
|
|
2d87e34aa2 | ||
|
|
088564f22f | ||
|
|
8c8c1158f4 | ||
|
|
8faa456eb2 | ||
|
|
4c61158326 | ||
|
|
6792abc00a | ||
|
|
63442aea1d | ||
|
|
a58443eaa8 | ||
|
|
14a9614c35 | ||
|
|
f1011b97b3 | ||
|
|
1935b1e09a | ||
|
|
ac9df9d5d8 | ||
|
|
30a800a4d0 | ||
|
|
69537b67a2 | ||
|
|
92982f98a8 | ||
|
|
ea5fa72224 | ||
|
|
5a1651d483 | ||
|
|
fcbf6dfb7f | ||
|
|
7aebf92a6f | ||
|
|
1b9f8d4f0b | ||
|
|
93db4eb986 | ||
|
|
347c2f22c1 | ||
|
|
a8ea191ffb | ||
|
|
2a4c22757b | ||
|
|
28ebbbc7d1 | ||
|
|
5e7d08f05d | ||
|
|
ea005748dc | ||
|
|
b021bfab5e | ||
|
|
29cd105a1d | ||
|
|
be2e68e96c | ||
|
|
9dd3f81bb7 | ||
|
|
5d918591d4 | ||
|
|
f1c027fa4d | ||
|
|
d2d3ccf68d | ||
|
|
f4efed54d5 | ||
|
|
459538e40c | ||
|
|
183f501761 | ||
|
|
65ee5f539b | ||
|
|
604339326c | ||
|
|
0acec1343f | ||
|
|
0434756bbd | ||
|
|
e233433140 | ||
|
|
c63f285f53 | ||
|
|
0800188718 | ||
|
|
6188e48beb | ||
|
|
5726e29b56 | ||
|
|
5d70399de0 | ||
|
|
73202dde5e | ||
|
|
a98ac8233c | ||
|
|
bee3e244fc | ||
|
|
da25999a15 | ||
|
|
62049df342 | ||
|
|
024e5aa90f | ||
|
|
6d791481d5 | ||
|
|
0a807d0893 | ||
|
|
23596bbdd4 | ||
|
|
fe79954138 | ||
|
|
9902c023ca | ||
|
|
e8baee6734 | ||
|
|
a8dc029e56 | ||
|
|
74e3fee7aa | ||
|
|
05aac696b7 | ||
|
|
48918f4727 | ||
|
|
303b5bd395 | ||
|
|
97733a52c8 | ||
|
|
89dcbec599 | ||
|
|
6497809fe1 | ||
|
|
9f3a63d8c4 | ||
|
|
748fa77d94 | ||
|
|
4ca86ee4eb | ||
|
|
ec2f372933 | ||
|
|
4267800db2 | ||
|
|
bb8cb9bcbb | ||
|
|
d5b9e15ee1 | ||
|
|
43ee3246f6 | ||
|
|
b56724dee5 | ||
|
|
92748ac872 | ||
|
|
b89686c287 | ||
|
|
a34692630b | ||
|
|
735208562a | ||
|
|
49826fdc56 | ||
|
|
e8c54a609c | ||
|
|
ece2c51358 | ||
|
|
3ec8af4661 | ||
|
|
77a08596fe | ||
|
|
879f699b2b | ||
|
|
d7383a3361 | ||
|
|
a5b321a93b | ||
|
|
5fa6b0ca1c | ||
|
|
27328afe3c | ||
|
|
2d26af1ae2 | ||
|
|
6db8f33e8f | ||
|
|
d6a7e6d52c | ||
|
|
df99b3b666 | ||
|
|
0f1c9f4fe2 | ||
|
|
5dcd5f79dc | ||
|
|
8a81171739 | ||
|
|
1c4f20f76f | ||
|
|
f84fa588f6 | ||
|
|
e30e34f342 | ||
|
|
fc93ced067 | ||
|
|
bb7df72d31 | ||
|
|
f8425658e4 | ||
|
|
53c8cf09b6 | ||
|
|
9f29bf4949 | ||
|
|
98e2adf794 | ||
|
|
2a43e0b0ed | ||
|
|
773ae73820 | ||
|
|
009db57bc5 | ||
|
|
5e98126e77 | ||
|
|
bd7ebfd83a | ||
|
|
10f41ef157 | ||
|
|
1dd4960109 | ||
|
|
75413b6c86 | ||
|
|
b2180582a7 | ||
|
|
8211ce7ae3 | ||
|
|
e6b1597fa7 | ||
|
|
8937d3cd9c | ||
|
|
51f320d147 | ||
|
|
e402a894bb | ||
|
|
9b577f1219 | ||
|
|
220f678403 | ||
|
|
4173fc4daa | ||
|
|
c6756d2145 | ||
|
|
6731f6eeb5 | ||
|
|
6f7f8b40e3 | ||
|
|
1a83b2c99b | ||
|
|
f641fae1c7 | ||
|
|
deb43d9872 | ||
|
|
cee4e1305e | ||
|
|
a1f989c43c | ||
|
|
b67abadbac | ||
|
|
8c29c85696 | ||
|
|
4fe4c298d7 | ||
|
|
13d35229d5 | ||
|
|
f0137b41b6 | ||
|
|
b221d21903 | ||
|
|
8bac202626 | ||
|
|
973151c949 | ||
|
|
ed26ab78a5 | ||
|
|
8454b2d235 | ||
|
|
91d0f89f60 | ||
|
|
e074672e86 | ||
|
|
6c1901fe5b | ||
|
|
49052be627 | ||
|
|
5b5b540630 | ||
|
|
9993bac3a1 | ||
|
|
3c95988693 | ||
|
|
fc5c3b470e | ||
|
|
53f05a72ba | ||
|
|
2c10ae7d06 | ||
|
|
6b9010c557 | ||
|
|
1bf0fdfa81 | ||
|
|
237759aac0 | ||
|
|
2a141e0a97 | ||
|
|
d6900be68e | ||
|
|
a35d7c7204 | ||
|
|
86287f9241 | ||
|
|
0b2e3dd96f | ||
|
|
90aa1d1ce7 | ||
|
|
ef2286ab53 | ||
|
|
47b25f3221 | ||
|
|
c30bfa12ce | ||
|
|
d0fc04251d | ||
|
|
dcbb41eb7a | ||
|
|
5c51259269 | ||
|
|
7eefa07052 | ||
|
|
999bdf8866 | ||
|
|
911c0c0fd9 | ||
|
|
99d8cc64a6 | ||
|
|
ba727d7568 | ||
|
|
ed01048f9f | ||
|
|
043ee3c58e | ||
|
|
6e0af7deda | ||
|
|
9591db2097 | ||
|
|
329a4c64f6 | ||
|
|
79015bc5ae | ||
|
|
27422ab9f9 | ||
|
|
abcb682498 | ||
|
|
5044127c46 | ||
|
|
0e4b8ca62e | ||
|
|
822017c69c | ||
|
|
eb6561b93d | ||
|
|
eb9d0c00a8 | ||
|
|
d24b1884a2 | ||
|
|
078534889e | ||
|
|
e92713006a | ||
|
|
18f43f3bc1 | ||
|
|
a4118b40e1 | ||
|
|
de29fbc324 | ||
|
|
3197dcf9b5 | ||
|
|
35aad409fd | ||
|
|
08ce6a7331 | ||
|
|
33a0099065 | ||
|
|
34d20fafda | ||
|
|
aafddcd0f0 | ||
|
|
0d6983b4ef | ||
|
|
69bfb72171 | ||
|
|
1aa33ec9b2 | ||
|
|
6702df1e22 | ||
|
|
c1748c9a86 | ||
|
|
9df624c62a | ||
|
|
0ee6197d7f | ||
|
|
b03a7dce3e | ||
|
|
6c59d7dd5f | ||
|
|
050191f0ef | ||
|
|
4b5a19ce5d | ||
|
|
7c4dd991b9 | ||
|
|
8455569e88 | ||
|
|
d25676559c | ||
|
|
a9437f7985 | ||
|
|
8141a97fc9 | ||
|
|
db842bd7e4 | ||
|
|
6dbec3a864 | ||
|
|
29f658cf4d | ||
|
|
ca83744a84 | ||
|
|
d91a9e2be4 | ||
|
|
8408c3f467 | ||
|
|
544c83a64c | ||
|
|
3800cd5e4f | ||
|
|
259f2cd419 | ||
|
|
20eb022c36 | ||
|
|
531e555b52 | ||
|
|
a9024aa34b | ||
|
|
d4e3b7842c | ||
|
|
167fddfbcc | ||
|
|
a48d642648 | ||
|
|
9a70f054c7 | ||
|
|
ca43d13bd6 | ||
|
|
5b71004179 | ||
|
|
63befccdbf | ||
|
|
4ecf7c02d0 | ||
|
|
f25badc18c | ||
|
|
6e931e9ba5 | ||
|
|
7e749124bf | ||
|
|
5822eb7808 | ||
|
|
7a7e086541 | ||
|
|
abab3167c2 | ||
|
|
8d08570568 | ||
|
|
2007078f13 | ||
|
|
dfb71a7978 | ||
|
|
480b0e3a03 | ||
|
|
8f8751f4ac | ||
|
|
de2ea112ee | ||
|
|
6f99a53fd9 | ||
|
|
a8a9b9032d | ||
|
|
6b15fb89de | ||
|
|
a711d6b8a1 | ||
|
|
5678f8aaa4 | ||
|
|
2fe37f6c26 | ||
|
|
a879747968 | ||
|
|
95e8fd7ee0 | ||
|
|
4416aaaa4c | ||
|
|
500d5f0efe | ||
|
|
fc8978fd90 | ||
|
|
73df126bd4 | ||
|
|
9146488c7d | ||
|
|
613a7fe376 | ||
|
|
ecb62f00d4 | ||
|
|
c4540a03cd | ||
|
|
3e31da99b5 | ||
|
|
098c1d0b1e | ||
|
|
178e908c86 | ||
|
|
ecf7cf14ae | ||
|
|
09e2a15a73 | ||
|
|
ab387860a6 | ||
|
|
f63fc94f2b | ||
|
|
41e5928cca | ||
|
|
8303175494 | ||
|
|
151eb6935b | ||
|
|
6a419c0c7b | ||
|
|
1795b32121 | ||
|
|
01971768ce | ||
|
|
ef7483ab01 | ||
|
|
527d11473d | ||
|
|
775dadc9a0 | ||
|
|
800b10a988 | ||
|
|
c977bf047d | ||
|
|
660a25f21d | ||
|
|
e7fd6d23af | ||
|
|
46982897f0 | ||
|
|
d24de68d64 | ||
|
|
7514c46a3f | ||
|
|
6632c0f8e3 | ||
|
|
79aafcda69 | ||
|
|
05af21e8dc | ||
|
|
0dc62cbbdc | ||
|
|
f3a084cfd2 | ||
|
|
8b32f82566 | ||
|
|
d598b6ed44 | ||
|
|
f5dc6f24b9 | ||
|
|
37454392da | ||
|
|
de7f9111d3 | ||
|
|
96d2889a6c | ||
|
|
f6412d1e9a | ||
|
|
b377cd6b1c | ||
|
|
f6cdbda5bb | ||
|
|
855c600a3e | ||
|
|
ea6e8303b0 | ||
|
|
d4934040d9 | ||
|
|
3449677b24 | ||
|
|
1ad3a6646e | ||
|
|
2d10f6b2bd | ||
|
|
5b05424d83 | ||
|
|
0826022d82 | ||
|
|
a901bfb9cb | ||
|
|
03cdce122a | ||
|
|
f2e0e16969 | ||
|
|
0c441e2ff3 | ||
|
|
21302304a5 | ||
|
|
6839d8b844 | ||
|
|
aee65a716c | ||
|
|
6a07d8f2c9 | ||
|
|
3c1ea81cd0 | ||
|
|
025f417bc7 | ||
|
|
c9dcd906c9 | ||
|
|
7024e04d15 | ||
|
|
0b8ac947db | ||
|
|
948410a064 | ||
|
|
2841339cac | ||
|
|
e8e82bd805 | ||
|
|
6876f40a0e | ||
|
|
5f4e1ecdfd | ||
|
|
044719432a | ||
|
|
d1a929da85 | ||
|
|
2a8978a60d | ||
|
|
c0afad7a26 | ||
|
|
37281c6c23 | ||
|
|
6de539a62d | ||
|
|
34704ec04d | ||
|
|
9fd6d46583 | ||
|
|
76a5e25656 | ||
|
|
3575b74837 | ||
|
|
f1c7996960 | ||
|
|
920f3581fa | ||
|
|
45e7af31fe | ||
|
|
67d5d8cdf1 | ||
|
|
9d8cadb7a9 | ||
|
|
6425c49d04 | ||
|
|
68d98b50f2 | ||
|
|
84986d393f | ||
|
|
115d488bc3 | ||
|
|
2eeb2213e3 | ||
|
|
1b48d661e8 | ||
|
|
49ba66dee9 | ||
|
|
46920f3bce | ||
|
|
4b955809f7 | ||
|
|
57d4d6546a | ||
|
|
9bfb58a764 | ||
|
|
0256ec0b8c | ||
|
|
b0b4a85d15 | ||
|
|
d40a058ef5 | ||
|
|
58b9efb24c | ||
|
|
17de785c12 | ||
|
|
c7ff1ba974 | ||
|
|
d17669f131 | ||
|
|
9755cd9ab4 | ||
|
|
6d2b18facc | ||
|
|
f8cf7034db | ||
|
|
a1e65c9fa7 | ||
|
|
499d2fe677 | ||
|
|
fe963edd9d | ||
|
|
96f006068f | ||
|
|
74f1fa5690 | ||
|
|
85c17b4cb0 | ||
|
|
6b3a1fd6d4 | ||
|
|
bcabcfce8c | ||
|
|
db0a3bf380 | ||
|
|
d5d9436e28 | ||
|
|
0827b067ec | ||
|
|
9d0dbe9210 | ||
|
|
1f7d1bf515 | ||
|
|
fb85ecf07b | ||
|
|
a931e6b316 | ||
|
|
3aa4644339 | ||
|
|
9a638c804a | ||
|
|
df3254c634 | ||
|
|
ba353b9f2b | ||
|
|
04c4e70dd1 | ||
|
|
d381e25e86 | ||
|
|
0c085f139a | ||
|
|
4123f4a5ce | ||
|
|
7bc269fda4 | ||
|
|
a22931bae6 | ||
|
|
403f886110 | ||
|
|
b7866be38d | ||
|
|
a1b415330e | ||
|
|
58318bb79f | ||
|
|
10bb30e190 | ||
|
|
199a2ffc46 | ||
|
|
f6ad2992f2 | ||
|
|
f039bd1239 | ||
|
|
da22d91ef3 | ||
|
|
cd360ec877 | ||
|
|
8e1ada4cdc | ||
|
|
ac063b4c79 | ||
|
|
10e6163e94 | ||
|
|
ebc3402307 | ||
|
|
d9c63bbcfe | ||
|
|
9c89e83c20 | ||
|
|
adc9bdeb68 | ||
|
|
ff7f0bdc63 | ||
|
|
c5f6980c69 | ||
|
|
2574354997 | ||
|
|
c4e42949cf | ||
|
|
1c5897f1cc | ||
|
|
510f99c7da | ||
|
|
1918346ae8 | ||
|
|
2a59515c72 | ||
|
|
7161152b41 | ||
|
|
b42660edab | ||
|
|
b405bbf98e | ||
|
|
c167938b61 | ||
|
|
24b531e6b2 | ||
|
|
9cffff715a | ||
|
|
804e912e19 | ||
|
|
d67e3900e3 | ||
|
|
e682f31898 | ||
|
|
a9053808b4 | ||
|
|
d9a62a0431 | ||
|
|
15ba73276d | ||
|
|
720dda784e | ||
|
|
0ae55404f5 | ||
|
|
9c41437870 | ||
|
|
da9cde083f | ||
|
|
ce3156c9fe | ||
|
|
be3752bf2f | ||
|
|
ef74db65aa | ||
|
|
867a233b6f | ||
|
|
59af25b2cd | ||
|
|
79c78518fb |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -9,17 +9,18 @@ Thumbs.db
|
||||
.DS_Store
|
||||
|
||||
# Eclipse project files
|
||||
#.classpath
|
||||
#.project
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Android Studio
|
||||
.idea/*
|
||||
!.idea/runConfigurations/
|
||||
!.idea/codeStyleSettings.xml
|
||||
.gradle
|
||||
build/
|
||||
*.iml
|
||||
.gitignore
|
||||
src/test/
|
||||
projectFilesBackup/
|
||||
29
.gitlab-ci.yml
Normal file
29
.gitlab-ci.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
image: registry.gitlab.com/fdroid/ci-images-base:latest
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .gradle/wrapper
|
||||
- .gradle/caches
|
||||
|
||||
before_script:
|
||||
- set -e
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
# Accept the license for the Android build tools
|
||||
- echo y | /opt/android-sdk/tools/bin/sdkmanager "build-tools;26.0.2"
|
||||
# Download OpenJDK 6 so we can compile against its standard library
|
||||
- JDK_FILE=openjdk-6-jre-headless_6b38-1.13.10-1~deb7u1_amd64.deb
|
||||
- if [ ! -d openjdk ]
|
||||
- then
|
||||
- wget -q http://ftp.uk.debian.org/debian/pool/main/o/openjdk-6/$JDK_FILE
|
||||
- dpkg-deb -x $JDK_FILE openjdk
|
||||
- fi
|
||||
- export JAVA_6_HOME=$PWD/openjdk/usr/lib/jvm/java-6-openjdk-amd64
|
||||
|
||||
test:
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
||||
after_script:
|
||||
# this file changes every time but should not be cached
|
||||
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
|
||||
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
|
||||
28
.idea/runConfigurations/All_tests.xml
generated
Normal file
28
.idea/runConfigurations/All_tests.xml
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="briar-android" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method>
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-j2se" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="bramble-api" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_bramble_core.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_bramble_core.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="bramble-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_bramble_j2se.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_bramble_j2se.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-j2se" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="bramble-j2se" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-j2se" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_briar_android.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_briar_android.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="briar-android" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
Normal file
23
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<module name="briar-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" value="-ea" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
|
||||
<option name="ENV_VARIABLES" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<envs />
|
||||
<patterns />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -6,99 +6,90 @@ apply plugin: 'witness'
|
||||
apply plugin: 'de.undercouch.download'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '26.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
targetSdkVersion 26
|
||||
versionCode 1618
|
||||
versionName "0.16.18"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':bramble-core')
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
|
||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
}
|
||||
|
||||
def torBinaryDir = 'src/main/res/raw'
|
||||
|
||||
task downloadTorGeoIp(type: Download) {
|
||||
src 'https://briarproject.org/build/geoip-2015-12-01.zip'
|
||||
dest "$torBinaryDir/geoip.zip"
|
||||
onlyIfNewer true
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
|
||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
|
||||
'org.jacoco:org.jacoco.ant:0.7.4.201502262128:org.jacoco.ant-0.7.4.201502262128.jar:013ce2a68ba57a3c59215ae0dec4df3498c078062a38c3b94c841fc14450f283',
|
||||
'org.jacoco:org.jacoco.core:0.7.4.201502262128:org.jacoco.core-0.7.4.201502262128.jar:ec4c74554312fac5116350164786f91b35c9e082fa4ea598bfa42b5db05d7abb',
|
||||
'org.jacoco:org.jacoco.report:0.7.4.201502262128:org.jacoco.report-0.7.4.201502262128.jar:7a3554c605e088e7e323b1084656243f0444fa353e2f2dee1f1a4204eb64ff09',
|
||||
'org.ow2.asm:asm-debug-all:5.0.1:asm-debug-all-5.0.1.jar:4734de5b515a454b0096db6971fb068e5f70e6f10bbee2b3bd2fdfe5d978ed57',
|
||||
]
|
||||
}
|
||||
|
||||
task downloadTorBinaryArm(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-arm.zip'
|
||||
dest "$torBinaryDir/tor_arm.zip"
|
||||
onlyIfNewer true
|
||||
ext.torBinaryDir = 'src/main/res/raw'
|
||||
ext.torVersion = '0.2.9.14'
|
||||
ext.geoipVersion = '2017-11-06'
|
||||
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
||||
|
||||
def torBinaries = [
|
||||
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
|
||||
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
|
||||
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
|
||||
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
|
||||
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||
]
|
||||
|
||||
def downloadBinary(name) {
|
||||
return tasks.create("downloadBinary${name}", Download) {
|
||||
src "${torDownloadUrl}${name}.zip"
|
||||
.replace('tor_', "tor-${torVersion}-")
|
||||
.replace('geoip', "geoip-${geoipVersion}")
|
||||
.replaceAll('_', '-')
|
||||
dest "${torBinaryDir}/${name}.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
}
|
||||
|
||||
task downloadTorBinaryArmPie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-arm-pie.zip'
|
||||
dest "$torBinaryDir/tor_arm_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-x86.zip'
|
||||
dest "$torBinaryDir/tor_x86.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86Pie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.7.6-x86-pie.zip'
|
||||
dest "$torBinaryDir/tor_x86_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task verifyTorGeoIp(type: Verify, dependsOn: 'downloadTorGeoIp') {
|
||||
src "$torBinaryDir/geoip.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '9bcdaf0a7ba0933735328d8ec466c25c25dbb459efc2bce9e55c774eabea5162'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
|
||||
src "$torBinaryDir/tor_arm.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '83272962eda701cd5d74d2418651c4ff0f0b1dff51f558a292d1a1c42bf12146'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
|
||||
src "$torBinaryDir/tor_arm_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'd0300d1e45de11ebb24ed62b9c492be9c2e88590b7822195ab38c7a76ffcf646'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
|
||||
src "$torBinaryDir/tor_x86.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'b8813d97b01ee1b9c9a4233c1b9bbe9f9f6b494ae6f9cbd84de8a3911911615e'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
|
||||
src "$torBinaryDir/tor_x86_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '9c66e765aa196dc089951a1b2140cc8290305c2fcbf365121f99e01a233baf4e'
|
||||
def verifyBinary(name, chksum) {
|
||||
return tasks.create([
|
||||
name : "verifyBinary${name}",
|
||||
type : Verify,
|
||||
dependsOn: downloadBinary(name)]) {
|
||||
src "${torBinaryDir}/${name}.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum chksum
|
||||
}
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
preBuild.dependsOn {
|
||||
[
|
||||
'verifyTorGeoIp',
|
||||
'verifyTorBinaryArm',
|
||||
'verifyTorBinaryArmPie',
|
||||
'verifyTorBinaryX86',
|
||||
'verifyTorBinaryX86Pie'
|
||||
]
|
||||
torBinaries.every { key, value ->
|
||||
preBuild.dependsOn.add(verifyBinary(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -11,8 +11,6 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<!-- Since API 23, this is needed to add contacts via Bluetooth -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||
@@ -22,6 +23,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
@@ -33,19 +35,20 @@ public class AndroidPluginModule {
|
||||
|
||||
@Provides
|
||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler,
|
||||
AndroidExecutor androidExecutor, SecureRandom random,
|
||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||
Application app, LocationUtils locationUtils, DevReporter reporter,
|
||||
EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||
androidExecutor, appContext, random, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
||||
locationUtils, reporter, eventBus, torSocketFactory,
|
||||
backoffFactory);
|
||||
androidExecutor, appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||
appContext, locationUtils, reporter, eventBus,
|
||||
torSocketFactory, backoffFactory);
|
||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||
backoffFactory, appContext);
|
||||
final Collection<DuplexPluginFactory> duplex =
|
||||
Collection<DuplexPluginFactory> duplex =
|
||||
Arrays.asList(bluetooth, tor, lan);
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@@ -11,8 +11,9 @@ import android.content.IntentFilter;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
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.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -23,6 +24,8 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
@@ -30,23 +33,14 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -61,12 +55,11 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
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;
|
||||
@@ -74,14 +67,10 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class DroidtoothPlugin implements DuplexPlugin {
|
||||
class DroidtoothPlugin implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||
private static final String FOUND =
|
||||
"android.bluetooth.device.action.FOUND";
|
||||
private static final String DISCOVERY_FINISHED =
|
||||
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
@@ -135,12 +124,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
// with a message queue, so submit it to the AndroidExecutor
|
||||
try {
|
||||
adapter = androidExecutor.runOnBackgroundThread(
|
||||
new Callable<BluetoothAdapter>() {
|
||||
@Override
|
||||
public BluetoothAdapter call() throws Exception {
|
||||
return BluetoothAdapter.getDefaultAdapter();
|
||||
}
|
||||
}).get();
|
||||
BluetoothAdapter::getDefaultAdapter).get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.warning("Interrupted while getting BluetoothAdapter");
|
||||
@@ -164,10 +148,8 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
bind();
|
||||
} else {
|
||||
// Enable Bluetooth if settings allow
|
||||
if (callback.getSettings().getBoolean("enable", false)) {
|
||||
wasEnabledByUs = true;
|
||||
if (adapter.enable()) LOG.info("Enabling Bluetooth");
|
||||
else LOG.info("Could not enable Bluetooth");
|
||||
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
|
||||
enableAdapter();
|
||||
} else {
|
||||
LOG.info("Not enabling Bluetooth");
|
||||
}
|
||||
@@ -175,40 +157,36 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning()) return;
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext,
|
||||
adapter);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
if (!StringUtils.isNullOrEmpty(address)) {
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ADDRESS, address);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
// Bind a server socket to accept connections from contacts
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", getUuid());
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
LOG.info("Socket bound");
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning()) return;
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext,
|
||||
adapter);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
if (!StringUtils.isNullOrEmpty(address)) {
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ADDRESS, address);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
// Bind a server socket to accept connections from contacts
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", getUuid());
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
LOG.info("Socket bound");
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -258,13 +236,27 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
private void enableAdapter() {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
tryToClose(socket);
|
||||
// Disable Bluetooth if we enabled it and it's still enabled
|
||||
if (wasEnabledByUs && adapter.isEnabled()) {
|
||||
disableAdapter();
|
||||
}
|
||||
|
||||
private void disableAdapter() {
|
||||
if (adapter != null && adapter.isEnabled() && wasEnabledByUs) {
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
else LOG.info("Could not disable Bluetooth");
|
||||
}
|
||||
@@ -293,21 +285,18 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
final ContactId c = e.getKey();
|
||||
ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
final String address = e.getValue().get(PROP_ADDRESS);
|
||||
String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
final String uuid = e.getValue().get(PROP_UUID);
|
||||
String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -362,8 +351,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
@@ -373,90 +361,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
if (!isRunning()) return null;
|
||||
// Use the invitation codes to generate the UUID
|
||||
byte[] b = r.nextBytes(UUID_BYTES);
|
||||
UUID uuid = UUID.nameUUIDFromBytes(b);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Invitation UUID " + uuid);
|
||||
// Bind a server socket for receiving invitation connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
// Create the background tasks
|
||||
CompletionService<BluetoothSocket> complete =
|
||||
new ExecutorCompletionService<>(ioExecutor);
|
||||
List<Future<BluetoothSocket>> futures = new ArrayList<>();
|
||||
if (alice) {
|
||||
// Return the first connected socket
|
||||
futures.add(complete.submit(new ListeningTask(ss)));
|
||||
futures.add(complete.submit(new DiscoveryTask(uuid.toString())));
|
||||
} else {
|
||||
// Return the first socket with readable data
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new ListeningTask(ss))));
|
||||
futures.add(complete.submit(new ReadableTask(
|
||||
new DiscoveryTask(uuid.toString()))));
|
||||
}
|
||||
BluetoothSocket chosen = null;
|
||||
try {
|
||||
Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS);
|
||||
if (f == null) return null; // No task completed within the timeout
|
||||
chosen = f.get();
|
||||
return new DroidtoothTransportConnection(this, chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while exchanging invitations");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
// Closing the socket will terminate the listener task
|
||||
tryToClose(ss);
|
||||
closeSockets(futures, chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeSockets(final List<Future<BluetoothSocket>> futures,
|
||||
@Nullable final BluetoothSocket chosen) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Future<BluetoothSocket> f : futures) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else {
|
||||
BluetoothSocket s = f.get();
|
||||
if (s != null && s != chosen) {
|
||||
LOG.info("Closing unwanted socket");
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
return;
|
||||
} catch (ExecutionException | IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
@@ -471,7 +375,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
// Bind a server socket for receiving invitation connections
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
@@ -512,6 +416,23 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
return StringUtils.macToString(mac);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof EnableBluetoothEvent) {
|
||||
enableAdapterAsync();
|
||||
} else if (e instanceof DisableBluetoothEvent) {
|
||||
disableAdapterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void enableAdapterAsync() {
|
||||
ioExecutor.execute(this::enableAdapter);
|
||||
}
|
||||
|
||||
private void disableAdapterAsync() {
|
||||
ioExecutor.execute(this::disableAdapter);
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@@ -535,115 +456,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private class DiscoveryTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final String uuid;
|
||||
|
||||
private DiscoveryTask(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws Exception {
|
||||
// Repeat discovery until we connect or get interrupted
|
||||
while (true) {
|
||||
// Discover nearby devices
|
||||
LOG.info("Discovering nearby devices");
|
||||
List<String> addresses = discoverDevices();
|
||||
if (addresses.isEmpty()) {
|
||||
LOG.info("No devices discovered");
|
||||
continue;
|
||||
}
|
||||
// Connect to any device with the right UUID
|
||||
for (String address : addresses) {
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
LOG.info("Outgoing connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> discoverDevices() throws InterruptedException {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(FOUND);
|
||||
filter.addAction(DISCOVERY_FINISHED);
|
||||
DiscoveryReceiver disco = new DiscoveryReceiver();
|
||||
appContext.registerReceiver(disco, filter);
|
||||
LOG.info("Starting discovery");
|
||||
adapter.startDiscovery();
|
||||
return disco.waitForAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiscoveryReceiver extends BroadcastReceiver {
|
||||
|
||||
private final CountDownLatch finished = new CountDownLatch(1);
|
||||
private final List<String> addresses = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(DISCOVERY_FINISHED)) {
|
||||
LOG.info("Discovery finished");
|
||||
ctx.unregisterReceiver(this);
|
||||
finished.countDown();
|
||||
} else if (action.equals(FOUND)) {
|
||||
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Discovered device: " +
|
||||
scrubMacAddress(d.getAddress()));
|
||||
}
|
||||
addresses.add(d.getAddress());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> waitForAddresses() throws InterruptedException {
|
||||
finished.await();
|
||||
List<String> shuffled = new ArrayList<>(addresses);
|
||||
Collections.shuffle(shuffled);
|
||||
return shuffled;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListeningTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final BluetoothServerSocket serverSocket;
|
||||
|
||||
private ListeningTask(BluetoothServerSocket serverSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws IOException {
|
||||
BluetoothSocket s = serverSocket.accept();
|
||||
LOG.info("Incoming connection");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReadableTask implements Callable<BluetoothSocket> {
|
||||
|
||||
private final Callable<BluetoothSocket> connectionTask;
|
||||
|
||||
private ReadableTask(Callable<BluetoothSocket> connectionTask) {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BluetoothSocket call() throws Exception {
|
||||
BluetoothSocket s = connectionTask.call();
|
||||
InputStream in = s.getInputStream();
|
||||
while (in.available() == 0) {
|
||||
LOG.info("Waiting for data");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
LOG.info("Data available");
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final BluetoothServerSocket ss;
|
||||
@@ -656,16 +468,13 @@ class DroidtoothPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return new Callable<KeyAgreementConnection>() {
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws IOException {
|
||||
BluetoothSocket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new DroidtoothTransportConnection(
|
||||
DroidtoothPlugin.this, s), ID);
|
||||
}
|
||||
return () -> {
|
||||
BluetoothSocket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new DroidtoothTransportConnection(
|
||||
DroidtoothPlugin.this, s), ID);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
@@ -31,15 +32,18 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
private final SecureRandom secureRandom;
|
||||
private final EventBus eventBus;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public DroidtoothPluginFactory(Executor ioExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
SecureRandom secureRandom, BackoffFactory backoffFactory) {
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
this.secureRandom = secureRandom;
|
||||
this.eventBus = eventBus;
|
||||
this.backoffFactory = backoffFactory;
|
||||
}
|
||||
|
||||
@@ -57,7 +61,10 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
|
||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import net.freehaven.tor.control.EventHandler;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
@@ -56,10 +55,16 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@@ -70,22 +75,33 @@ import javax.net.SocketFactory;
|
||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WIFI;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
|
||||
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
private static final String PROP_ONION = "onion";
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
|
||||
};
|
||||
@@ -96,6 +112,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
Logger.getLogger(TorPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
private final DevReporter reporter;
|
||||
@@ -108,6 +125,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||
private final File doneFile, cookieFile;
|
||||
private final PowerManager.WakeLock wakeLock;
|
||||
private final Lock connectionStatusLock;
|
||||
private final AtomicReference<Future<?>> connectivityCheck =
|
||||
new AtomicReference<>();
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
@@ -116,12 +136,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private volatile TorControlConnection controlConnection = null;
|
||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||
|
||||
TorPlugin(Executor ioExecutor, Context appContext,
|
||||
LocationUtils locationUtils, DevReporter reporter,
|
||||
SocketFactory torSocketFactory, Backoff backoff,
|
||||
DuplexPluginCallback callback, String architecture, int maxLatency,
|
||||
int maxIdleTime) {
|
||||
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||
Context appContext, LocationUtils locationUtils,
|
||||
DevReporter reporter, SocketFactory torSocketFactory,
|
||||
Backoff backoff, DuplexPluginCallback callback,
|
||||
String architecture, int maxLatency, int maxIdleTime) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.appContext = appContext;
|
||||
this.locationUtils = locationUtils;
|
||||
this.reporter = reporter;
|
||||
@@ -143,8 +164,10 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
|
||||
Object o = appContext.getSystemService(POWER_SERVICE);
|
||||
PowerManager pm = (PowerManager) o;
|
||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "TorPlugin");
|
||||
// This tag will prevent Huawei's powermanager from killing us.
|
||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||
wakeLock.setReferenceCounted(false);
|
||||
connectionStatusLock = new ReentrantLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,19 +205,31 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
String torPath = torFile.getAbsolutePath();
|
||||
String configPath = configFile.getAbsolutePath();
|
||||
String pid = String.valueOf(android.os.Process.myPid());
|
||||
String[] cmd = {torPath, "-f", configPath, OWNER, pid};
|
||||
String[] env = {"HOME=" + torDirectory.getAbsolutePath()};
|
||||
Process torProcess;
|
||||
ProcessBuilder pb =
|
||||
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
|
||||
Map<String, String> env = pb.environment();
|
||||
env.put("HOME", torDirectory.getAbsolutePath());
|
||||
pb.directory(torDirectory);
|
||||
try {
|
||||
torProcess = Runtime.getRuntime().exec(cmd, env, torDirectory);
|
||||
torProcess = pb.start();
|
||||
} catch (SecurityException | IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
// Log the process's standard output until it detaches
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
while (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
||||
if (stdout.hasNextLine()) {
|
||||
LOG.info(stdout.nextLine());
|
||||
}
|
||||
if (stderr.hasNextLine()) {
|
||||
LOG.info(stderr.nextLine());
|
||||
}
|
||||
}
|
||||
stdout.close();
|
||||
stderr.close();
|
||||
}
|
||||
try {
|
||||
// Wait for the process to detach or exit
|
||||
@@ -238,7 +273,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
// Register to receive network status events
|
||||
networkStateReceiver = new NetworkStateReceiver();
|
||||
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(CONNECTIVITY_ACTION);
|
||||
filter.addAction(ACTION_SCREEN_ON);
|
||||
filter.addAction(ACTION_SCREEN_OFF);
|
||||
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||
appContext.registerReceiver(networkStateReceiver, filter);
|
||||
// Bind a server socket to receive incoming hidden service connections
|
||||
bind();
|
||||
@@ -351,57 +390,45 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private void sendDevReports() {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO: Trigger this with a TransportEnabledEvent
|
||||
File reportDir = AndroidUtils.getReportDir(appContext);
|
||||
reporter.sendReports(reportDir);
|
||||
}
|
||||
ioExecutor.execute(() -> {
|
||||
// TODO: Trigger this with a TransportEnabledEvent
|
||||
File reportDir = AndroidUtils.getReportDir(appContext);
|
||||
reporter.sendReports(reportDir);
|
||||
});
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
String portString = callback.getSettings().get("port");
|
||||
int port;
|
||||
if (StringUtils.isNullOrEmpty(portString)) port = 0;
|
||||
else port = Integer.parseInt(portString);
|
||||
// Bind a server socket to receive connections from Tor
|
||||
ServerSocket ss = null;
|
||||
try {
|
||||
ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress("127.0.0.1", port));
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
// Store the port number
|
||||
final String localPort = String.valueOf(ss.getLocalPort());
|
||||
Settings s = new Settings();
|
||||
s.put("port", localPort);
|
||||
callback.mergeSettings(s);
|
||||
// Create a hidden service if necessary
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
publishHiddenService(localPort);
|
||||
}
|
||||
});
|
||||
backoff.reset();
|
||||
// Accept incoming hidden service connections from Tor
|
||||
acceptContactConnections(ss);
|
||||
ioExecutor.execute(() -> {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
String portString = callback.getSettings().get(PREF_TOR_PORT);
|
||||
int port;
|
||||
if (StringUtils.isNullOrEmpty(portString)) port = 0;
|
||||
else port = Integer.parseInt(portString);
|
||||
// Bind a server socket to receive connections from Tor
|
||||
ServerSocket ss = null;
|
||||
try {
|
||||
ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress("127.0.0.1", port));
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
// Store the port number
|
||||
String localPort = String.valueOf(ss.getLocalPort());
|
||||
Settings s = new Settings();
|
||||
s.put(PREF_TOR_PORT, localPort);
|
||||
callback.mergeSettings(s);
|
||||
// Create a hidden service if necessary
|
||||
ioExecutor.execute(() -> publishHiddenService(localPort));
|
||||
backoff.reset();
|
||||
// Accept incoming hidden service connections from Tor
|
||||
acceptContactConnections(ss);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -521,20 +548,21 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!isRunning()) return;
|
||||
backoff.increment();
|
||||
// TODO: Pass properties to connectAndCallBack()
|
||||
for (ContactId c : callback.getRemoteProperties().keySet())
|
||||
if (!connected.contains(c)) connectAndCallBack(c);
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndCallBack(final ContactId c) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DuplexTransportConnection d = createConnection(c);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
}
|
||||
private void connectAndCallBack(ContactId c, TransportProperties p) {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -542,8 +570,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||
if (p == null) return null;
|
||||
return createConnection(callback.getRemoteProperties(c));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DuplexTransportConnection createConnection(TransportProperties p) {
|
||||
String onion = p.get(PROP_ONION);
|
||||
if (StringUtils.isNullOrEmpty(onion)) return null;
|
||||
if (!ONION.matcher(onion).matches()) {
|
||||
@@ -571,17 +602,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInvitations() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
@@ -618,6 +638,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||
if (status.equals("CLOSED") || status.equals("FAILED"))
|
||||
updateConnectionStatus(); // Check whether we've lost connectivity
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -657,7 +679,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event, String path) {
|
||||
public void onEvent(int event, @Nullable String path) {
|
||||
stopWatching();
|
||||
latch.countDown();
|
||||
}
|
||||
@@ -666,7 +688,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
if (((SettingsUpdatedEvent) e).getNamespace().equals("tor")) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString())) {
|
||||
LOG.info("Tor settings updated");
|
||||
updateConnectionStatus();
|
||||
}
|
||||
@@ -674,58 +697,75 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private void updateConnectionStatus() {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
|
||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
boolean online = net != null && net.isConnected();
|
||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||
String country = locationUtils.getCurrentCountry();
|
||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||
country);
|
||||
Settings s = callback.getSettings();
|
||||
boolean useMobileData = s.getBoolean("torOverMobile", true);
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
if ("".equals(country)) LOG.info("Country code unknown");
|
||||
else LOG.info("Country code: " + country);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
enableNetwork(false);
|
||||
} else if (blocked) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
enableNetwork(false);
|
||||
} else if (!wifi && !useMobileData) {
|
||||
LOG.info("Disabling network due to data setting");
|
||||
enableNetwork(false);
|
||||
} else {
|
||||
LOG.info("Enabling network");
|
||||
enableNetwork(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
try {
|
||||
connectionStatusLock.lock();
|
||||
updateConnectionStatusLocked();
|
||||
} finally {
|
||||
connectionStatusLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Locking: connectionStatusLock
|
||||
private void updateConnectionStatusLocked() {
|
||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
boolean online = net != null && net.isConnected();
|
||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||
String country = locationUtils.getCurrentCountry();
|
||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||
country);
|
||||
Settings s = callback.getSettings();
|
||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
if ("".equals(country)) LOG.info("Country code unknown");
|
||||
else LOG.info("Country code: " + country);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
enableNetwork(false);
|
||||
} else if (blocked) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
enableNetwork(false);
|
||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||
LOG.info("Disabling network due to data setting");
|
||||
enableNetwork(false);
|
||||
} else {
|
||||
LOG.info("Enabling network");
|
||||
enableNetwork(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleConnectionStatusUpdate() {
|
||||
Future<?> newConnectivityCheck =
|
||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||
Future<?> oldConnectivityCheck =
|
||||
connectivityCheck.getAndSet(newConnectivityCheck);
|
||||
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
|
||||
}
|
||||
|
||||
private class NetworkStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent i) {
|
||||
if (!running) return;
|
||||
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
||||
LOG.info("Detected connectivity change");
|
||||
updateConnectionStatus();
|
||||
String action = i.getAction();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
|
||||
updateConnectionStatus();
|
||||
if (ACTION_SCREEN_ON.equals(action)
|
||||
|| ACTION_SCREEN_OFF.equals(action)) {
|
||||
scheduleConnectionStatusUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
@@ -36,6 +37,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
private final DevReporter reporter;
|
||||
@@ -43,11 +45,13 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
private final SocketFactory torSocketFactory;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public TorPluginFactory(Executor ioExecutor, Context appContext,
|
||||
public TorPluginFactory(Executor ioExecutor,
|
||||
ScheduledExecutorService scheduler, Context appContext,
|
||||
LocationUtils locationUtils, DevReporter reporter,
|
||||
EventBus eventBus, SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.appContext = appContext;
|
||||
this.locationUtils = locationUtils;
|
||||
this.reporter = reporter;
|
||||
@@ -89,9 +93,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
|
||||
reporter, torSocketFactory, backoff, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME);
|
||||
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
||||
locationUtils, reporter, torSocketFactory, backoff, callback,
|
||||
architecture, MAX_LATENCY, MAX_IDLE_TIME);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -21,12 +22,12 @@ class TorTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return socket.getInputStream();
|
||||
return IoUtils.getInputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
return IoUtils.getOutputStream(socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,14 +27,11 @@ class AndroidExecutorImpl implements AndroidExecutor {
|
||||
@Inject
|
||||
AndroidExecutorImpl(Application app) {
|
||||
uiHandler = new Handler(app.getApplicationContext().getMainLooper());
|
||||
loop = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
backgroundHandler = new Handler();
|
||||
startLatch.countDown();
|
||||
Looper.loop();
|
||||
}
|
||||
loop = () -> {
|
||||
Looper.prepare();
|
||||
backgroundHandler = new Handler();
|
||||
startLatch.countDown();
|
||||
Looper.loop();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import android.app.Application;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiConfiguration;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.provider.Settings.Secure.ANDROID_ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
|
||||
|
||||
private static final int SEED_LENGTH = 32;
|
||||
|
||||
private final Context appContext;
|
||||
|
||||
@Inject
|
||||
AndroidSecureRandomProvider(Application app) {
|
||||
appContext = app.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
super.writeToEntropyPool(out);
|
||||
out.writeInt(android.os.Process.myPid());
|
||||
out.writeInt(android.os.Process.myTid());
|
||||
out.writeInt(android.os.Process.myUid());
|
||||
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
|
||||
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
|
||||
ContentResolver contentResolver = appContext.getContentResolver();
|
||||
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
|
||||
if (id != null) out.writeUTF(id);
|
||||
Parcel parcel = Parcel.obtain();
|
||||
WifiManager wm =
|
||||
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
|
||||
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
|
||||
if (configs != null) {
|
||||
for (WifiConfiguration config : configs)
|
||||
parcel.writeParcelable(config, 0);
|
||||
}
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt != null) {
|
||||
for (BluetoothDevice device : bt.getBondedDevices())
|
||||
parcel.writeParcelable(device, 0);
|
||||
}
|
||||
out.write(parcel.marshall());
|
||||
parcel.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeSeed() {
|
||||
super.writeSeed();
|
||||
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
|
||||
applyOpenSslFix();
|
||||
}
|
||||
|
||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||
private void applyOpenSslFix() {
|
||||
byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed(
|
||||
SEED_LENGTH);
|
||||
try {
|
||||
// Seed the OpenSSL PRNG
|
||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_seed", byte[].class)
|
||||
.invoke(null, seed);
|
||||
// Mix the output of the Linux PRNG into the OpenSSL PRNG
|
||||
int bytesRead = (Integer) Class.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_load_file", String.class, long.class)
|
||||
.invoke(null, "/dev/urandom", 1024);
|
||||
if (bytesRead != 1024) throw new IOException();
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package org.briarproject.bramble.system;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.provider.Settings.Secure.ANDROID_ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class AndroidSeedProvider extends LinuxSeedProvider {
|
||||
|
||||
private final Context appContext;
|
||||
|
||||
@Inject
|
||||
AndroidSeedProvider(Application app) {
|
||||
appContext = app.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeToEntropyPool(DataOutputStream out) throws IOException {
|
||||
out.writeInt(android.os.Process.myPid());
|
||||
out.writeInt(android.os.Process.myTid());
|
||||
out.writeInt(android.os.Process.myUid());
|
||||
if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
|
||||
if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
|
||||
ContentResolver contentResolver = appContext.getContentResolver();
|
||||
String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
|
||||
if (id != null) out.writeUTF(id);
|
||||
super.writeToEntropyPool(out);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import android.app.Application;
|
||||
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -16,8 +16,8 @@ public class AndroidSystemModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SeedProvider provideSeedProvider(Application app) {
|
||||
return new AndroidSeedProvider(app);
|
||||
SecureRandomProvider provideSecureRandomProvider(Application app) {
|
||||
return new AndroidSecureRandomProvider(app);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
apply plugin: 'java'
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
apply plugin: 'java-library'
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
|
||||
apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
compile "com.google.dagger:dagger:2.0.2"
|
||||
compile 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
compile 'com.google.code.findbugs:jsr305:3.0.1'
|
||||
implementation "com.google.dagger:dagger:2.0.2"
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.jmock:jmock:2.8.1"
|
||||
testCompile "org.jmock:jmock-junit4:2.8.1"
|
||||
testCompile "org.jmock:jmock-legacy:2.8.1"
|
||||
testCompile "org.hamcrest:hamcrest-library:1.3"
|
||||
testCompile "org.hamcrest:hamcrest-core:1.3"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jmock:jmock:2.8.2"
|
||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
||||
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
||||
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||
'com.google.code.findbugs:jsr305:c885ce34249682bc0236b4a7d56efcc12048e6135a5baf7a9cde8ad8cda13fcd',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
||||
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
|
||||
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
|
||||
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
|
||||
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
||||
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
||||
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
||||
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -39,3 +48,8 @@ task jarTest(type: Jar, dependsOn: testClasses) {
|
||||
artifacts {
|
||||
testOutput jarTest
|
||||
}
|
||||
|
||||
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
|
||||
tasks.withType(JavaCompile) {
|
||||
useJava6StandardLibrary(it)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
@@ -53,6 +54,12 @@ public class Bytes implements Comparable<Bytes> {
|
||||
return aBytes.length - bBytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() +
|
||||
"(" + StringUtils.toHexString(getBytes()) + ")";
|
||||
}
|
||||
|
||||
public static class BytesComparator implements Comparator<Bytes> {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,7 +23,7 @@ public class BdfMessageContext {
|
||||
}
|
||||
|
||||
public BdfMessageContext(BdfDictionary dictionary) {
|
||||
this(dictionary, Collections.<MessageId>emptyList());
|
||||
this(dictionary, Collections.emptyList());
|
||||
}
|
||||
|
||||
public BdfDictionary getDictionary() {
|
||||
|
||||
@@ -10,8 +10,6 @@ public interface CryptoComponent {
|
||||
|
||||
SecretKey generateSecretKey();
|
||||
|
||||
PseudoRandom getPseudoRandom(int seed1, int seed2);
|
||||
|
||||
SecureRandom getSecureRandom();
|
||||
|
||||
KeyPair generateAgreementKeyPair();
|
||||
@@ -24,15 +22,6 @@ public interface CryptoComponent {
|
||||
|
||||
KeyParser getMessageKeyParser();
|
||||
|
||||
/** Generates a random invitation code. */
|
||||
int generateBTInvitationCode();
|
||||
|
||||
/**
|
||||
* Derives a confirmation code from the given master secret.
|
||||
* @param alice whether the code is for use by Alice or Bob.
|
||||
*/
|
||||
int deriveBTConfirmationCode(SecretKey master, boolean alice);
|
||||
|
||||
/**
|
||||
* Derives a stream header key from the given master secret.
|
||||
* @param alice whether the key is for use by Alice or Bob.
|
||||
@@ -137,7 +126,8 @@ public interface CryptoComponent {
|
||||
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
||||
|
||||
/** Encodes the pseudo-random tag that is used to recognise a stream. */
|
||||
void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber);
|
||||
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||
long streamNumber);
|
||||
|
||||
/**
|
||||
* Signs the given byte[] with the given PrivateKey.
|
||||
|
||||
@@ -6,9 +6,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
public interface PasswordStrengthEstimator {
|
||||
|
||||
float NONE = 0;
|
||||
float WEAK = 0.4f;
|
||||
float QUITE_WEAK = 0.6f;
|
||||
float QUITE_STRONG = 0.8f;
|
||||
float WEAK = 0.25f;
|
||||
float QUITE_WEAK = 0.5f;
|
||||
float QUITE_STRONG = 0.75f;
|
||||
float STRONG = 1;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.briarproject.bramble.api.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A deterministic pseudo-random number generator.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface PseudoRandom {
|
||||
|
||||
byte[] nextBytes(int bytes);
|
||||
}
|
||||
@@ -14,8 +14,9 @@ public interface StreamDecrypterFactory {
|
||||
StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates a {@link StreamDecrypter} for decrypting an invitation stream.
|
||||
* Creates a {@link StreamDecrypter} for decrypting a contact exchange
|
||||
* stream.
|
||||
*/
|
||||
StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ public interface StreamEncrypterFactory {
|
||||
StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates a {@link StreamEncrypter} for encrypting an invitation stream.
|
||||
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
|
||||
* stream.
|
||||
*/
|
||||
StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
public class BdfDictionary extends ConcurrentSkipListMap<String, Object> {
|
||||
@NotThreadSafe
|
||||
public class BdfDictionary extends TreeMap<String, Object> {
|
||||
|
||||
public static final Object NULL_VALUE = new Object();
|
||||
|
||||
|
||||
@@ -3,15 +3,17 @@ package org.briarproject.bramble.api.data;
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||
|
||||
public class BdfList extends Vector<Object> {
|
||||
@NotThreadSafe
|
||||
public class BdfList extends ArrayList<Object> {
|
||||
|
||||
/**
|
||||
* Factory method for constructing lists inline.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when the database uses a newer schema than the current code.
|
||||
*/
|
||||
public class DataTooNewException extends DbException {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when the database uses an older schema than the current code and
|
||||
* cannot be migrated.
|
||||
*/
|
||||
public class DataTooOldException extends DbException {
|
||||
}
|
||||
@@ -37,6 +37,11 @@ public interface DatabaseComponent {
|
||||
|
||||
/**
|
||||
* Opens the database and returns true if the database already existed.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open() throws DbException;
|
||||
|
||||
@@ -122,8 +127,9 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Deletes the message with the given ID. The message ID and any other
|
||||
* associated data are not deleted.
|
||||
* Deletes the message with the given ID. Unlike
|
||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID and any
|
||||
* other associated data are not deleted.
|
||||
*/
|
||||
void deleteMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
@@ -452,6 +458,11 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a message (and all associated state) from the database.
|
||||
*/
|
||||
void removeMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a transport (and all associated state) from the database.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
public class Metadata extends Hashtable<String, byte[]> {
|
||||
@NotThreadSafe
|
||||
public class Metadata extends TreeMap<String, byte[]> {
|
||||
|
||||
/**
|
||||
* Special value to indicate that a key is being removed.
|
||||
|
||||
@@ -45,7 +45,7 @@ public class Transaction {
|
||||
* committed.
|
||||
*/
|
||||
public void attach(Event e) {
|
||||
if (events == null) events = new ArrayList<Event>();
|
||||
if (events == null) events = new ArrayList<>();
|
||||
events.add(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
public class Author {
|
||||
|
||||
public enum Status {ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES}
|
||||
public enum Status {
|
||||
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
|
||||
}
|
||||
|
||||
private final AuthorId id;
|
||||
private final String name;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
public interface InvitationConstants {
|
||||
|
||||
/**
|
||||
* The connection timeout in milliseconds.
|
||||
*/
|
||||
long CONNECTION_TIMEOUT = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The confirmation timeout in milliseconds.
|
||||
*/
|
||||
long CONFIRMATION_TIMEOUT = 60 * 1000;
|
||||
|
||||
/**
|
||||
* The number of bits in an invitation or confirmation code. Codes must fit
|
||||
* into six decimal digits.
|
||||
*/
|
||||
int CODE_BITS = 19;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
/**
|
||||
* An interface for receiving updates about the state of an
|
||||
* {@link InvitationTask}.
|
||||
*/
|
||||
public interface InvitationListener {
|
||||
|
||||
/** Called if a connection to the remote peer is established. */
|
||||
void connectionSucceeded();
|
||||
|
||||
/**
|
||||
* Called if a connection to the remote peer cannot be established. This
|
||||
* indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void connectionFailed();
|
||||
|
||||
/** Called if key agreement with the remote peer succeeds. */
|
||||
void keyAgreementSucceeded(int localCode, int remoteCode);
|
||||
|
||||
/**
|
||||
* Called if key agreement with the remote peer fails or the connection is
|
||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void keyAgreementFailed();
|
||||
|
||||
/** Called if the remote peer's confirmation check succeeds. */
|
||||
void remoteConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Called if remote peer's confirmation check fails or the connection is
|
||||
* lost. This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void remoteConfirmationFailed();
|
||||
|
||||
/**
|
||||
* Called if the exchange of pseudonyms succeeds. This indicates that the
|
||||
* protocol has ended successfully.
|
||||
*/
|
||||
void pseudonymExchangeSucceeded(String remoteName);
|
||||
|
||||
/**
|
||||
* Called if the exchange of pseudonyms fails or the connection is lost.
|
||||
* This indicates that the protocol has ended unsuccessfully.
|
||||
*/
|
||||
void pseudonymExchangeFailed();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A snapshot of the state of an {@link InvitationTask}.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class InvitationState {
|
||||
|
||||
private final int localInvitationCode, remoteInvitationCode;
|
||||
private final int localConfirmationCode, remoteConfirmationCode;
|
||||
private final boolean connected, connectionFailed;
|
||||
private final boolean localCompared, remoteCompared;
|
||||
private final boolean localMatched, remoteMatched;
|
||||
@Nullable
|
||||
private final String contactName;
|
||||
|
||||
public InvitationState(int localInvitationCode, int remoteInvitationCode,
|
||||
int localConfirmationCode, int remoteConfirmationCode,
|
||||
boolean connected, boolean connectionFailed, boolean localCompared,
|
||||
boolean remoteCompared, boolean localMatched,
|
||||
boolean remoteMatched, @Nullable String contactName) {
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
this.localConfirmationCode = localConfirmationCode;
|
||||
this.remoteConfirmationCode = remoteConfirmationCode;
|
||||
this.connected = connected;
|
||||
this.connectionFailed = connectionFailed;
|
||||
this.localCompared = localCompared;
|
||||
this.remoteCompared = remoteCompared;
|
||||
this.localMatched = localMatched;
|
||||
this.remoteMatched = remoteMatched;
|
||||
this.contactName = contactName;
|
||||
}
|
||||
|
||||
public int getLocalInvitationCode() {
|
||||
return localInvitationCode;
|
||||
}
|
||||
|
||||
public int getRemoteInvitationCode() {
|
||||
return remoteInvitationCode;
|
||||
}
|
||||
|
||||
public int getLocalConfirmationCode() {
|
||||
return localConfirmationCode;
|
||||
}
|
||||
|
||||
public int getRemoteConfirmationCode() {
|
||||
return remoteConfirmationCode;
|
||||
}
|
||||
|
||||
public boolean getConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
public boolean getConnectionFailed() {
|
||||
return connectionFailed;
|
||||
}
|
||||
|
||||
public boolean getLocalCompared() {
|
||||
return localCompared;
|
||||
}
|
||||
|
||||
public boolean getRemoteCompared() {
|
||||
return remoteCompared;
|
||||
}
|
||||
|
||||
public boolean getLocalMatched() {
|
||||
return localMatched;
|
||||
}
|
||||
|
||||
public boolean getRemoteMatched() {
|
||||
return remoteMatched;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getContactName() {
|
||||
return contactName;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* A task for exchanging invitations with a remote peer.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface InvitationTask {
|
||||
|
||||
/**
|
||||
* Adds a listener to be informed of state changes and returns the
|
||||
* task's current state.
|
||||
*/
|
||||
InvitationState addListener(InvitationListener l);
|
||||
|
||||
/**
|
||||
* Removes the given listener.
|
||||
*/
|
||||
void removeListener(InvitationListener l);
|
||||
|
||||
/**
|
||||
* Asynchronously starts the connection process.
|
||||
*/
|
||||
void connect();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes matched.
|
||||
*/
|
||||
void localConfirmationSucceeded();
|
||||
|
||||
/**
|
||||
* Asynchronously informs the remote peer that the local peer's
|
||||
* confirmation codes did not match.
|
||||
*/
|
||||
void localConfirmationFailed();
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.bramble.api.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* Creates tasks for exchanging invitations with remote peers.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface InvitationTaskFactory {
|
||||
|
||||
/**
|
||||
* Creates a task using the given local and remote invitation codes.
|
||||
*/
|
||||
InvitationTask createTask(int localCode, int remoteCode);
|
||||
}
|
||||
@@ -9,4 +9,5 @@ public interface BluetoothConstants {
|
||||
String PROP_ADDRESS = "address";
|
||||
String PROP_UUID = "uuid";
|
||||
|
||||
String PREF_BT_ENABLE = "enable";
|
||||
}
|
||||
|
||||
@@ -4,4 +4,10 @@ public interface LanTcpConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.lan");
|
||||
|
||||
// a transport property (shared with contacts)
|
||||
String PROP_IP_PORTS = "ipPorts";
|
||||
|
||||
// a local setting
|
||||
String PREF_LAN_IP_PORTS = "ipPorts";
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ public interface PluginCallback {
|
||||
*/
|
||||
Map<ContactId, TransportProperties> getRemoteProperties();
|
||||
|
||||
/**
|
||||
* Returns the plugin's remote transport properties for the given contact.
|
||||
*/
|
||||
TransportProperties getRemoteProperties(ContactId c);
|
||||
|
||||
/**
|
||||
* Merges the given settings with the namespaced settings
|
||||
*/
|
||||
|
||||
@@ -32,11 +32,6 @@ public interface PluginManager {
|
||||
*/
|
||||
Collection<DuplexPlugin> getDuplexPlugins();
|
||||
|
||||
/**
|
||||
* Returns any duplex plugins that support invitations.
|
||||
*/
|
||||
Collection<DuplexPlugin> getInvitationPlugins();
|
||||
|
||||
/**
|
||||
* Returns any duplex plugins that support key agreement.
|
||||
*/
|
||||
|
||||
@@ -4,8 +4,19 @@ public interface TorConstants {
|
||||
|
||||
TransportId ID = new TransportId("org.briarproject.bramble.tor");
|
||||
|
||||
String PROP_ONION = "onion";
|
||||
|
||||
int SOCKS_PORT = 59050;
|
||||
int CONTROL_PORT = 59051;
|
||||
|
||||
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
||||
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
|
||||
|
||||
String PREF_TOR_NETWORK = "network";
|
||||
String PREF_TOR_PORT = "port";
|
||||
|
||||
int PREF_TOR_NETWORK_NEVER = 0;
|
||||
int PREF_TOR_NETWORK_WIFI = 1;
|
||||
int PREF_TOR_NETWORK_ALWAYS = 2;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.bramble.api.plugin.duplex;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -23,20 +22,6 @@ public interface DuplexPlugin extends Plugin {
|
||||
@Nullable
|
||||
DuplexTransportConnection createConnection(ContactId c);
|
||||
|
||||
/**
|
||||
* Returns true if the plugin supports exchanging invitations.
|
||||
*/
|
||||
boolean supportsInvitations();
|
||||
|
||||
/**
|
||||
* Attempts to create and return an invitation connection to the remote
|
||||
* peer. Returns null if no connection can be established within the given
|
||||
* time.
|
||||
*/
|
||||
@Nullable
|
||||
DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||
long timeout, boolean alice);
|
||||
|
||||
/**
|
||||
* Returns true if the plugin supports short-range key agreement.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that asks the Bluetooth plugin to disable the Bluetooth adapter if
|
||||
* we previously enabled it.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class DisableBluetoothEvent extends Event {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class EnableBluetoothEvent extends Event {
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public interface TransportPropertyManager {
|
||||
/**
|
||||
* Returns the local transport properties for all transports.
|
||||
* <br/>
|
||||
* Read-Only
|
||||
* TODO: Transaction can be read-only when code is simplified
|
||||
*/
|
||||
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
|
||||
throws DbException;
|
||||
@@ -49,6 +49,13 @@ public interface TransportPropertyManager {
|
||||
Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the remote transport properties for the given contact and
|
||||
* transport.
|
||||
*/
|
||||
TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given properties with the existing local properties for the
|
||||
* given transport.
|
||||
|
||||
@@ -22,7 +22,7 @@ public class MessageContext {
|
||||
}
|
||||
|
||||
public MessageContext(Metadata metadata) {
|
||||
this(metadata, Collections.<MessageId>emptyList());
|
||||
this(metadata, Collections.emptyList());
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.briarproject.bramble.api.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Wrapper for a platform-specific secure random number generator.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface SecureRandomProvider {
|
||||
|
||||
/**
|
||||
* Returns a {@link Provider} that provides a strong {@link SecureRandom}
|
||||
* implementation, or null if the platform's default implementation should
|
||||
* be used.
|
||||
*/
|
||||
@Nullable
|
||||
Provider getProvider();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.bramble.api.system;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
/**
|
||||
* Uses a platform-specific source to provide a seed for a pseudo-random
|
||||
* number generator.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface SeedProvider {
|
||||
|
||||
/**
|
||||
* The length of the seed in bytes.
|
||||
*/
|
||||
int SEED_BYTES = 32;
|
||||
|
||||
byte[] getSeed();
|
||||
}
|
||||
@@ -15,9 +15,9 @@ public interface StreamReaderFactory {
|
||||
InputStream createStreamReader(InputStream in, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates an {@link InputStream InputStream} for reading from an
|
||||
* invitation stream.
|
||||
* Creates an {@link InputStream InputStream} for reading from a contact
|
||||
* exchangestream.
|
||||
*/
|
||||
InputStream createInvitationStreamReader(InputStream in,
|
||||
InputStream createContactExchangeStreamReader(InputStream in,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ public interface StreamWriterFactory {
|
||||
OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
|
||||
|
||||
/**
|
||||
* Creates an {@link OutputStream OutputStream} for writing to an
|
||||
* invitation stream.
|
||||
* Creates an {@link OutputStream OutputStream} for writing to a contact
|
||||
* exchange stream.
|
||||
*/
|
||||
OutputStream createInvitationStreamWriter(OutputStream out,
|
||||
OutputStream createContactExchangeStreamWriter(OutputStream out,
|
||||
SecretKey headerKey);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
|
||||
public interface TransportConstants {
|
||||
|
||||
/**
|
||||
* The current version of the transport protocol.
|
||||
*/
|
||||
int PROTOCOL_VERSION = 3;
|
||||
|
||||
/**
|
||||
* The length of the pseudo-random tag in bytes.
|
||||
*/
|
||||
@@ -14,21 +19,22 @@ public interface TransportConstants {
|
||||
*/
|
||||
int STREAM_HEADER_NONCE_LENGTH = 24;
|
||||
|
||||
/**
|
||||
* The length of the stream header initialisation vector (IV) in bytes.
|
||||
*/
|
||||
int STREAM_HEADER_IV_LENGTH = STREAM_HEADER_NONCE_LENGTH - 8;
|
||||
|
||||
/**
|
||||
* The length of the message authentication code (MAC) in bytes.
|
||||
*/
|
||||
int MAC_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* The length of the stream header plaintext in bytes. The stream header
|
||||
* contains the protocol version, stream number and frame key.
|
||||
*/
|
||||
int STREAM_HEADER_PLAINTEXT_LENGTH = 2 + 8 + SecretKey.LENGTH;
|
||||
|
||||
/**
|
||||
* The length of the stream header in bytes.
|
||||
*/
|
||||
int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH
|
||||
+ MAC_LENGTH;
|
||||
int STREAM_HEADER_LENGTH = STREAM_HEADER_NONCE_LENGTH
|
||||
+ STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH;
|
||||
|
||||
/**
|
||||
* The length of the frame nonce in bytes.
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -59,4 +60,24 @@ public class IoUtils {
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for a bug in Android 7, see
|
||||
// https://android-review.googlesource.com/#/c/271775/
|
||||
public static InputStream getInputStream(Socket s) throws IOException {
|
||||
try {
|
||||
return s.getInputStream();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for a bug in Android 7, see
|
||||
// https://android-review.googlesource.com/#/c/271775/
|
||||
public static OutputStream getOutputStream(Socket s) throws IOException {
|
||||
try {
|
||||
return s.getOutputStream();
|
||||
} catch (NullPointerException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class PrivacyUtils {
|
||||
|
||||
@Nullable
|
||||
public static String scrubMacAddress(@Nullable String address) {
|
||||
if (address == null) return null;
|
||||
if (address == null || address.length() == 0) return null;
|
||||
// this is a fake address we need to know about
|
||||
if (address.equals("02:00:00:00:00:00")) return address;
|
||||
// keep first and last octet of MAC address
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -27,6 +28,7 @@ public class StringUtils {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
private static final Random random = new Random();
|
||||
|
||||
public static boolean isNullOrEmpty(@Nullable String s) {
|
||||
return s == null || s.length() == 0;
|
||||
@@ -139,4 +141,12 @@ public class StringUtils {
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public static String getRandomString(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
c[i] = (char) ('a' + random.nextInt(26));
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ package org.briarproject.bramble.test;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.After;
|
||||
|
||||
public abstract class BrambleMockTestCase extends
|
||||
BrambleTestCase {
|
||||
public abstract class BrambleMockTestCase extends BrambleTestCase {
|
||||
|
||||
protected final Mockery context = new Mockery();
|
||||
|
||||
|
||||
@@ -8,12 +8,9 @@ public abstract class BrambleTestCase {
|
||||
|
||||
public BrambleTestCase() {
|
||||
// Ensure exceptions thrown on worker threads cause tests to fail
|
||||
UncaughtExceptionHandler fail = new UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
UncaughtExceptionHandler fail = (thread, throwable) -> {
|
||||
throwable.printStackTrace();
|
||||
fail();
|
||||
};
|
||||
Thread.setDefaultUncaughtExceptionHandler(fail);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,27 @@ package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
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.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
private static final AtomicInteger nextTestDir =
|
||||
@@ -34,15 +49,54 @@ public class TestUtils {
|
||||
return getRandomBytes(UniqueId.LENGTH);
|
||||
}
|
||||
|
||||
public static String getRandomString(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
c[i] = (char) ('a' + random.nextInt(26));
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
public static SecretKey getSecretKey() {
|
||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||
}
|
||||
|
||||
public static LocalAuthor getLocalAuthor() {
|
||||
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||
}
|
||||
|
||||
public static LocalAuthor getLocalAuthor(int nameLength) {
|
||||
AuthorId id = new AuthorId(getRandomId());
|
||||
String name = getRandomString(nameLength);
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
long created = System.currentTimeMillis();
|
||||
return new LocalAuthor(id, name, publicKey, privateKey, created);
|
||||
}
|
||||
|
||||
public static Author getAuthor() {
|
||||
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||
}
|
||||
|
||||
public static Author getAuthor(int nameLength) {
|
||||
AuthorId id = new AuthorId(getRandomId());
|
||||
String name = getRandomString(nameLength);
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
return new Author(id, name, publicKey);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId) {
|
||||
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||
return getGroup(clientId, descriptorLength);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
||||
GroupId groupId = new GroupId(getRandomId());
|
||||
byte[] descriptor = getRandomBytes(descriptorLength);
|
||||
return new Group(groupId, clientId, descriptor);
|
||||
}
|
||||
|
||||
public static Message getMessage(GroupId groupId) {
|
||||
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
|
||||
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
|
||||
}
|
||||
|
||||
public static Message getMessage(GroupId groupId, int rawLength) {
|
||||
MessageId id = new MessageId(getRandomId());
|
||||
byte[] raw = getRandomBytes(rawLength);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return new Message(id, groupId, timestamp, raw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,54 @@
|
||||
plugins {
|
||||
id "java"
|
||||
id "net.ltgt.apt" version "0.9"
|
||||
id "idea"
|
||||
}
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
apply plugin: 'java-library'
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
|
||||
apply plugin: 'net.ltgt.apt'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
compile project(':bramble-api')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
compile 'com.madgag.spongycastle:core:1.54.0.0'
|
||||
compile 'com.h2database:h2:1.4.190'
|
||||
implementation project(path: ':bramble-api', configuration: 'default')
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
|
||||
testCompile project(path: ':bramble-api', configuration: 'testOutput')
|
||||
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
|
||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jmock:jmock:2.8.2"
|
||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
||||
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
||||
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
||||
|
||||
testApt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.madgag.spongycastle:core:1e7fa4b19ccccd1011364ab838d0b4702470c178bbbdd94c5c90b2d4d749ea1e',
|
||||
'com.h2database:h2:23ba495a07bbbb3bd6c3084d10a96dad7a23741b8b6d64b213459a784195a98c'
|
||||
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
||||
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
|
||||
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
|
||||
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
|
||||
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
||||
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
||||
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
||||
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -35,3 +63,8 @@ task jarTest(type: Jar, dependsOn: testClasses) {
|
||||
artifacts {
|
||||
testOutput jarTest
|
||||
}
|
||||
|
||||
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
|
||||
tasks.withType(JavaCompile) {
|
||||
useJava6StandardLibrary(it)
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -8,7 +8,6 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||
import org.briarproject.bramble.db.DatabaseModule;
|
||||
import org.briarproject.bramble.event.EventModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.invitation.InvitationModule;
|
||||
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.plugin.PluginModule;
|
||||
@@ -32,7 +31,6 @@ import dagger.Module;
|
||||
DatabaseExecutorModule.class,
|
||||
EventModule.class,
|
||||
IdentityModule.class,
|
||||
InvitationModule.class,
|
||||
KeyAgreementModule.class,
|
||||
LifecycleModule.class,
|
||||
PluginModule.class,
|
||||
@@ -54,6 +52,7 @@ public class BrambleCoreModule {
|
||||
c.inject(new IdentityModule.EagerSingletons());
|
||||
c.inject(new LifecycleModule.EagerSingletons());
|
||||
c.inject(new PluginModule.EagerSingletons());
|
||||
c.inject(new PropertiesModule.EagerSingletons());
|
||||
c.inject(new SyncModule.EagerSingletons());
|
||||
c.inject(new SystemModule.EagerSingletons());
|
||||
c.inject(new TransportModule.EagerSingletons());
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
|
||||
/**
|
||||
* An {@link Executor} that delegates its tasks to another {@link Executor}
|
||||
* while limiting the number of tasks that are delegated concurrently. Tasks
|
||||
* are delegated in the order they are submitted to this executor.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public class PoliteExecutor implements Executor {
|
||||
|
||||
private static final Level LOG_LEVEL = FINE;
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
private final Queue<Runnable> queue = new LinkedList<>();
|
||||
private final Executor delegate;
|
||||
private final int maxConcurrentTasks;
|
||||
private final Logger log;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private int concurrentTasks = 0;
|
||||
|
||||
/**
|
||||
* @param tag the tag to be used for logging
|
||||
* @param delegate the executor to which tasks will be delegated
|
||||
* @param maxConcurrentTasks the maximum number of tasks that will be
|
||||
* delegated concurrently. If this is set to 1, tasks submitted to this
|
||||
* executor will run in the order they are submitted and will not run
|
||||
* concurrently
|
||||
*/
|
||||
public PoliteExecutor(String tag, Executor delegate,
|
||||
int maxConcurrentTasks) {
|
||||
this.delegate = delegate;
|
||||
this.maxConcurrentTasks = maxConcurrentTasks;
|
||||
log = Logger.getLogger(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable r) {
|
||||
long submitted = System.currentTimeMillis();
|
||||
Runnable wrapped = () -> {
|
||||
if (log.isLoggable(LOG_LEVEL)) {
|
||||
long queued = System.currentTimeMillis() - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
}
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
scheduleNext();
|
||||
}
|
||||
};
|
||||
synchronized (lock) {
|
||||
if (concurrentTasks < maxConcurrentTasks) {
|
||||
concurrentTasks++;
|
||||
delegate.execute(wrapped);
|
||||
} else {
|
||||
queue.add(wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleNext() {
|
||||
synchronized (lock) {
|
||||
Runnable next = queue.poll();
|
||||
if (next == null) concurrentTasks--;
|
||||
else delegate.execute(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.briarproject.bramble;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
|
||||
@NotNullByDefault
|
||||
public class TimeLoggingExecutor extends ThreadPoolExecutor {
|
||||
|
||||
private static final Level LOG_LEVEL = FINE;
|
||||
|
||||
private final Logger log;
|
||||
|
||||
public TimeLoggingExecutor(String tag, int corePoolSize, int maxPoolSize,
|
||||
long keepAliveTime, TimeUnit unit,
|
||||
BlockingQueue<Runnable> workQueue,
|
||||
RejectedExecutionHandler handler) {
|
||||
super(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue,
|
||||
handler);
|
||||
log = Logger.getLogger(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable r) {
|
||||
if (log.isLoggable(LOG_LEVEL)) {
|
||||
long submitted = System.currentTimeMillis();
|
||||
super.execute(() -> {
|
||||
long started = System.currentTimeMillis();
|
||||
long queued = started - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
r.run();
|
||||
long executing = System.currentTimeMillis() - started;
|
||||
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
|
||||
});
|
||||
} else {
|
||||
super.execute(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,8 +201,7 @@ class ClientHelperImpl implements ClientHelper {
|
||||
public Map<MessageId, BdfDictionary> getMessageMetadataAsDictionary(
|
||||
Transaction txn, GroupId g) throws DbException, FormatException {
|
||||
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g);
|
||||
Map<MessageId, BdfDictionary> parsed =
|
||||
new HashMap<MessageId, BdfDictionary>(raw.size());
|
||||
Map<MessageId, BdfDictionary> parsed = new HashMap<>(raw.size());
|
||||
for (Entry<MessageId, Metadata> e : raw.entrySet())
|
||||
parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
|
||||
return parsed;
|
||||
@@ -229,8 +228,7 @@ class ClientHelperImpl implements ClientHelper {
|
||||
FormatException {
|
||||
Metadata metadata = metadataEncoder.encode(query);
|
||||
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g, metadata);
|
||||
Map<MessageId, BdfDictionary> parsed =
|
||||
new HashMap<MessageId, BdfDictionary>(raw.size());
|
||||
Map<MessageId, BdfDictionary> parsed = new HashMap<>(raw.size());
|
||||
for (Entry<MessageId, Metadata> e : raw.entrySet())
|
||||
parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
|
||||
return parsed;
|
||||
|
||||
@@ -80,7 +80,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
private volatile boolean alice;
|
||||
|
||||
@Inject
|
||||
public ContactExchangeTaskImpl(DatabaseComponent db,
|
||||
ContactExchangeTaskImpl(DatabaseComponent db,
|
||||
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||
ConnectionManager connectionManager, ContactManager contactManager,
|
||||
@@ -146,12 +146,12 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
|
||||
// Create the readers
|
||||
InputStream streamReader =
|
||||
streamReaderFactory.createInvitationStreamReader(in,
|
||||
streamReaderFactory.createContactExchangeStreamReader(in,
|
||||
alice ? bobHeaderKey : aliceHeaderKey);
|
||||
BdfReader r = bdfReaderFactory.createReader(streamReader);
|
||||
// Create the writers
|
||||
OutputStream streamWriter =
|
||||
streamWriterFactory.createInvitationStreamWriter(out,
|
||||
streamWriterFactory.createContactExchangeStreamWriter(out,
|
||||
alice ? aliceHeaderKey : bobHeaderKey);
|
||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
||||
|
||||
@@ -184,12 +184,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
// Close the outgoing stream and expect EOF on the incoming stream
|
||||
w.close();
|
||||
if (!r.eof()) LOG.warning("Unexpected data at end of connection");
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn, true);
|
||||
@@ -276,8 +271,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
|
||||
private Map<TransportId, TransportProperties> receiveTransportProperties(
|
||||
BdfReader r) throws IOException {
|
||||
Map<TransportId, TransportProperties> remote =
|
||||
new HashMap<TransportId, TransportProperties>();
|
||||
Map<TransportId, TransportProperties> remote = new HashMap<>();
|
||||
r.readListStart();
|
||||
while (!r.hasListEnd()) {
|
||||
r.readListStart();
|
||||
|
||||
@@ -34,8 +34,8 @@ class ContactManagerImpl implements ContactManager {
|
||||
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
|
||||
this.db = db;
|
||||
this.keyManager = keyManager;
|
||||
addHooks = new CopyOnWriteArrayList<AddContactHook>();
|
||||
removeHooks = new CopyOnWriteArrayList<RemoveContactHook>();
|
||||
addHooks = new CopyOnWriteArrayList<>();
|
||||
removeHooks = new CopyOnWriteArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,7 +125,7 @@ class ContactManagerImpl implements ContactManager {
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
List<Contact> active = new ArrayList<Contact>(contacts.size());
|
||||
List<Contact> active = new ArrayList<>(contacts.size());
|
||||
for (Contact c : contacts) if (c.isActive()) active.add(c);
|
||||
return active;
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
|
||||
/**
|
||||
* A {@link SecureRandom} implementation that combines the outputs of two or
|
||||
* more other implementations using XOR.
|
||||
*/
|
||||
class CombinedSecureRandom extends SecureRandom {
|
||||
|
||||
private static final Provider PROVIDER = new CombinedProvider();
|
||||
|
||||
CombinedSecureRandom(SecureRandom... randoms) {
|
||||
super(new CombinedSecureRandomSpi(randoms), PROVIDER);
|
||||
}
|
||||
|
||||
private static class CombinedSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private final SecureRandom[] randoms;
|
||||
|
||||
private CombinedSecureRandomSpi(SecureRandom... randoms) {
|
||||
if (randoms.length < 2) throw new IllegalArgumentException();
|
||||
this.randoms = randoms;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int numBytes) {
|
||||
byte[] combined = new byte[numBytes];
|
||||
for (SecureRandom random : randoms) {
|
||||
byte[] b = random.generateSeed(numBytes);
|
||||
int length = Math.min(numBytes, b.length);
|
||||
for (int i = 0; i < length; i++)
|
||||
combined[i] = (byte) (combined[i] ^ b[i]);
|
||||
}
|
||||
return combined;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] b) {
|
||||
byte[] temp = new byte[b.length];
|
||||
for (SecureRandom random : randoms) {
|
||||
random.nextBytes(temp);
|
||||
for (int i = 0; i < b.length; i++)
|
||||
b[i] = (byte) (b[i] ^ temp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
for (SecureRandom random : randoms) random.setSeed(seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CombinedProvider extends Provider {
|
||||
|
||||
private CombinedProvider() {
|
||||
super("Combined", 1.0, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,10 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
@@ -29,7 +28,10 @@ import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -38,12 +40,13 @@ import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CODE_BITS;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
class CryptoComponentImpl implements CryptoComponent {
|
||||
@@ -63,9 +66,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return s.getBytes(Charset.forName("US-ASCII"));
|
||||
}
|
||||
|
||||
// KDF labels for bluetooth confirmation code derivation
|
||||
private static final byte[] BT_A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
|
||||
private static final byte[] BT_B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
|
||||
// KDF labels for contact exchange stream header key derivation
|
||||
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||
@@ -101,16 +101,26 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private final MessageEncrypter messageEncrypter;
|
||||
|
||||
@Inject
|
||||
CryptoComponentImpl(SeedProvider seedProvider) {
|
||||
if (!FortunaSecureRandom.selfTest()) throw new RuntimeException();
|
||||
SecureRandom platformSecureRandom = new SecureRandom();
|
||||
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
String provider = platformSecureRandom.getProvider().getName();
|
||||
String algorithm = platformSecureRandom.getAlgorithm();
|
||||
LOG.info("Default SecureRandom: " + provider + " " + algorithm);
|
||||
SecureRandom defaultSecureRandom = new SecureRandom();
|
||||
String name = defaultSecureRandom.getProvider().getName();
|
||||
String algorithm = defaultSecureRandom.getAlgorithm();
|
||||
LOG.info("Default SecureRandom: " + name + " " + algorithm);
|
||||
}
|
||||
SecureRandom fortuna = new FortunaSecureRandom(seedProvider.getSeed());
|
||||
secureRandom = new CombinedSecureRandom(platformSecureRandom, fortuna);
|
||||
Provider provider = secureRandomProvider.getProvider();
|
||||
if (provider == null) {
|
||||
LOG.info("Using default");
|
||||
} else {
|
||||
installSecureRandomProvider(provider);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
SecureRandom installedSecureRandom = new SecureRandom();
|
||||
String name = installedSecureRandom.getProvider().getName();
|
||||
String algorithm = installedSecureRandom.getAlgorithm();
|
||||
LOG.info("Installed SecureRandom: " + name + " " + algorithm);
|
||||
}
|
||||
}
|
||||
secureRandom = new SecureRandom();
|
||||
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
|
||||
PARAMETERS, secureRandom);
|
||||
agreementKeyPairGenerator = new ECKeyPairGenerator();
|
||||
@@ -124,6 +134,31 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
messageEncrypter = new MessageEncrypter(secureRandom);
|
||||
}
|
||||
|
||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||
private void installSecureRandomProvider(Provider provider) {
|
||||
Provider[] providers = Security.getProviders("SecureRandom.SHA1PRNG");
|
||||
if (providers == null || providers.length == 0
|
||||
|| !provider.getClass().equals(providers[0].getClass())) {
|
||||
Security.insertProviderAt(provider, 1);
|
||||
}
|
||||
// Check the new provider is the default when no algorithm is specified
|
||||
SecureRandom random = new SecureRandom();
|
||||
if (!provider.getClass().equals(random.getProvider().getClass())) {
|
||||
throw new SecurityException("Wrong SecureRandom provider: "
|
||||
+ random.getProvider().getClass());
|
||||
}
|
||||
// Check the new provider is the default when SHA1PRNG is specified
|
||||
try {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SecurityException(e);
|
||||
}
|
||||
if (!provider.getClass().equals(random.getProvider().getClass())) {
|
||||
throw new SecurityException("Wrong SHA1PRNG provider: "
|
||||
+ random.getProvider().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[SecretKey.LENGTH];
|
||||
@@ -131,11 +166,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return new SecretKey(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
|
||||
return new PseudoRandomImpl(seed1, seed2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecureRandom getSecureRandom() {
|
||||
return secureRandom;
|
||||
@@ -207,20 +237,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return messageEncrypter.getKeyParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int generateBTInvitationCode() {
|
||||
int codeBytes = (CODE_BITS + 7) / 8;
|
||||
byte[] random = new byte[codeBytes];
|
||||
secureRandom.nextBytes(random);
|
||||
return ByteUtils.readUint(random, CODE_BITS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deriveBTConfirmationCode(SecretKey master, boolean alice) {
|
||||
byte[] b = macKdf(master, alice ? BT_A_CONFIRM : BT_B_CONFIRM);
|
||||
return ByteUtils.readUint(b, CODE_BITS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveHeaderKey(SecretKey master,
|
||||
boolean alice) {
|
||||
@@ -296,7 +312,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
|
||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||
return deriveMasterSecret(deriveSharedSecret(
|
||||
theirPublicKey,ourKeyPair, alice));
|
||||
theirPublicKey, ourKeyPair, alice));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -371,8 +387,11 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encodeTag(byte[] tag, SecretKey tagKey, long streamNumber) {
|
||||
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||
long streamNumber) {
|
||||
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
||||
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
// Initialise the PRF
|
||||
@@ -380,10 +399,14 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
// The output of the PRF must be long enough to use as a tag
|
||||
int macLength = prf.getDigestSize();
|
||||
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
||||
// The input is the stream number as a 64-bit integer
|
||||
byte[] input = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(streamNumber, input, 0);
|
||||
prf.update(input, 0, input.length);
|
||||
// The input is the protocol version as a 16-bit integer, followed by
|
||||
// the stream number as a 64-bit integer
|
||||
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
||||
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
||||
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
||||
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
||||
byte[] mac = new byte[macLength];
|
||||
prf.doFinal(mac, 0);
|
||||
// The output is the first TAG_LENGTH bytes of the MAC
|
||||
@@ -579,8 +602,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
// Package access for testing
|
||||
int chooseIterationCount(int targetMillis) {
|
||||
List<Long> quickSamples = new ArrayList<Long>(PBKDF_SAMPLES);
|
||||
List<Long> slowSamples = new ArrayList<Long>(PBKDF_SAMPLES);
|
||||
List<Long> quickSamples = new ArrayList<>(PBKDF_SAMPLES);
|
||||
List<Long> slowSamples = new ArrayList<>(PBKDF_SAMPLES);
|
||||
long iterationNanos = 0, initNanos = 0;
|
||||
while (iterationNanos <= 0 || initNanos <= 0) {
|
||||
// Sample the running time with one iteration and two iterations
|
||||
@@ -607,7 +630,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
private long sampleRunningTime(int iterations) {
|
||||
byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
|
||||
byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
|
||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||
int keyLengthInBits = SecretKey.LENGTH * 8;
|
||||
long start = System.nanoTime();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.system.SeedProvider;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@@ -31,26 +32,29 @@ public class CryptoModule {
|
||||
public static class EagerSingletons {
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
Executor cryptoExecutor;
|
||||
ExecutorService cryptoExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of executor threads.
|
||||
* <p>
|
||||
* The number of available processors can change during the lifetime of the
|
||||
* JVM, so this is just a reasonable guess.
|
||||
*/
|
||||
private static final int MAX_EXECUTOR_THREADS =
|
||||
Runtime.getRuntime().availableProcessors();
|
||||
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||
|
||||
private final ExecutorService cryptoExecutor;
|
||||
|
||||
public CryptoModule() {
|
||||
// Use an unbounded queue
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Create a limited # of threads and keep them in the pool for 60 secs
|
||||
cryptoExecutor = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
|
||||
60, SECONDS, queue, policy);
|
||||
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
|
||||
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -60,8 +64,9 @@ public class CryptoModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CryptoComponent provideCryptoComponent(SeedProvider seedProvider) {
|
||||
return new CryptoComponentImpl(seedProvider);
|
||||
CryptoComponent provideCryptoComponent(
|
||||
SecureRandomProvider secureRandomProvider) {
|
||||
return new CryptoComponentImpl(secureRandomProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -84,11 +89,18 @@ public class CryptoModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@CryptoExecutor
|
||||
Executor getCryptoExecutor(LifecycleManager lifecycleManager) {
|
||||
ExecutorService getCryptoExecutorService(
|
||||
LifecycleManager lifecycleManager) {
|
||||
lifecycleManager.registerForShutdown(cryptoExecutor);
|
||||
return cryptoExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CryptoExecutor
|
||||
Executor getCryptoExecutor() {
|
||||
return cryptoExecutor;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SecureRandom getSecureRandom(CryptoComponent crypto) {
|
||||
return crypto.getSecureRandom();
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A message digest that prevents length extension attacks - see Ferguson and
|
||||
* Schneier, <i>Practical Cryptography</i>, chapter 6.
|
||||
* <p>
|
||||
* "Let h be an interative hash function. The hash function h<sub>d</sub> is
|
||||
* defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
|
||||
* min(k, n/2) where k is the security level of h and n is the size of the hash
|
||||
* result."
|
||||
*/
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class DoubleDigest implements Digest {
|
||||
|
||||
private final Digest delegate;
|
||||
|
||||
DoubleDigest(Digest delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
private byte[] digest() {
|
||||
byte[] digest = new byte[delegate.getDigestSize()];
|
||||
delegate.doFinal(digest, 0); // h(m)
|
||||
delegate.update(digest, 0, digest.length);
|
||||
delegate.doFinal(digest, 0); // h(h(m))
|
||||
return digest;
|
||||
}
|
||||
|
||||
public int digest(byte[] buf, int offset, int len) {
|
||||
byte[] digest = digest();
|
||||
len = Math.min(len, digest.length);
|
||||
System.arraycopy(digest, 0, buf, offset, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDigestSize() {
|
||||
return delegate.getDigestSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithmName() {
|
||||
return "Double " + delegate.getAlgorithmName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
delegate.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte input) {
|
||||
delegate.update(input);
|
||||
}
|
||||
|
||||
public void update(byte[] input) {
|
||||
delegate.update(input, 0, input.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] input, int offset, int len) {
|
||||
delegate.update(input, offset, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int doFinal(byte[] out, int outOff) {
|
||||
return digest(out, outOff, delegate.getDigestSize());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* Implements the Fortuna pseudo-random number generator, as described in
|
||||
* Ferguson and Schneier, <i>Practical Cryptography</i>, chapter 9.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class FortunaGenerator {
|
||||
|
||||
private static final int MAX_BYTES_PER_REQUEST = 1024 * 1024;
|
||||
private static final int KEY_BYTES = 32;
|
||||
private static final int BLOCK_BYTES = 16;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private final DoubleDigest digest = new DoubleDigest(new SHA256Digest());
|
||||
private final BlockCipher cipher = new AESLightEngine();
|
||||
private final byte[] key = new byte[KEY_BYTES];
|
||||
private final byte[] counter = new byte[BLOCK_BYTES];
|
||||
private final byte[] buffer = new byte[BLOCK_BYTES];
|
||||
private final byte[] newKey = new byte[KEY_BYTES];
|
||||
|
||||
FortunaGenerator(byte[] seed) {
|
||||
reseed(seed);
|
||||
}
|
||||
|
||||
void reseed(byte[] seed) {
|
||||
lock.lock();
|
||||
try {
|
||||
digest.update(key);
|
||||
digest.update(seed);
|
||||
digest.digest(key, 0, KEY_BYTES);
|
||||
incrementCounter();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
void incrementCounter() {
|
||||
lock.lock();
|
||||
try {
|
||||
counter[0]++;
|
||||
for (int i = 0; counter[i] == 0; i++) {
|
||||
if (i + 1 == BLOCK_BYTES)
|
||||
throw new RuntimeException("Counter exhausted");
|
||||
counter[i + 1]++;
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
byte[] getCounter() {
|
||||
lock.lock();
|
||||
try {
|
||||
return counter;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int nextBytes(byte[] dest, int off, int len) {
|
||||
lock.lock();
|
||||
try {
|
||||
// Don't write more than the maximum number of bytes in one request
|
||||
if (len > MAX_BYTES_PER_REQUEST) len = MAX_BYTES_PER_REQUEST;
|
||||
cipher.init(true, new KeyParameter(key));
|
||||
// Generate full blocks directly into the output buffer
|
||||
int fullBlocks = len / BLOCK_BYTES;
|
||||
for (int i = 0; i < fullBlocks; i++) {
|
||||
cipher.processBlock(counter, 0, dest, off + i * BLOCK_BYTES);
|
||||
incrementCounter();
|
||||
}
|
||||
// Generate a partial block if needed
|
||||
int done = fullBlocks * BLOCK_BYTES, remaining = len - done;
|
||||
if (remaining >= BLOCK_BYTES) throw new AssertionError();
|
||||
if (remaining > 0) {
|
||||
cipher.processBlock(counter, 0, buffer, 0);
|
||||
incrementCounter();
|
||||
// Copy the partial block to the output buffer and erase our copy
|
||||
System.arraycopy(buffer, 0, dest, off + done, remaining);
|
||||
for (int i = 0; i < BLOCK_BYTES; i++) buffer[i] = 0;
|
||||
}
|
||||
// Generate a new key
|
||||
for (int i = 0; i < KEY_BYTES / BLOCK_BYTES; i++) {
|
||||
cipher.processBlock(counter, 0, newKey, i * BLOCK_BYTES);
|
||||
incrementCounter();
|
||||
}
|
||||
System.arraycopy(newKey, 0, key, 0, KEY_BYTES);
|
||||
for (int i = 0; i < KEY_BYTES; i++) newKey[i] = 0;
|
||||
// Return the number of bytes written
|
||||
return len;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SecureRandomSpi;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@link java.security.SecureRandom SecureRandom} implementation based on a
|
||||
* {@link FortunaGenerator}.
|
||||
*/
|
||||
class FortunaSecureRandom extends SecureRandom {
|
||||
|
||||
// Package access for testing
|
||||
static final byte[] SELF_TEST_VECTOR_1 =
|
||||
StringUtils.fromHexString("4BD6EA599D47E3EE9DD911833C29CA22");
|
||||
static final byte[] SELF_TEST_VECTOR_2 =
|
||||
StringUtils.fromHexString("10984D576E6850E505CA9F42A9BFD88A");
|
||||
static final byte[] SELF_TEST_VECTOR_3 =
|
||||
StringUtils.fromHexString("1E12DA166BD86DCECDE50A8296018DE2");
|
||||
|
||||
private static final Provider PROVIDER = new FortunaProvider();
|
||||
|
||||
FortunaSecureRandom(byte[] seed) {
|
||||
super(new FortunaSecureRandomSpi(seed), PROVIDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the {@link #nextBytes(byte[])} and {@link #setSeed(byte[])}
|
||||
* methods are passed through to the generator in the expected way.
|
||||
*/
|
||||
static boolean selfTest() {
|
||||
byte[] seed = new byte[32];
|
||||
SecureRandom r = new FortunaSecureRandom(seed);
|
||||
byte[] output = new byte[16];
|
||||
r.nextBytes(output);
|
||||
if (!Arrays.equals(SELF_TEST_VECTOR_1, output)) return false;
|
||||
r.nextBytes(output);
|
||||
if (!Arrays.equals(SELF_TEST_VECTOR_2, output)) return false;
|
||||
r.setSeed(seed);
|
||||
r.nextBytes(output);
|
||||
return Arrays.equals(SELF_TEST_VECTOR_3, output);
|
||||
}
|
||||
|
||||
private static class FortunaSecureRandomSpi extends SecureRandomSpi {
|
||||
|
||||
private final FortunaGenerator generator;
|
||||
|
||||
private FortunaSecureRandomSpi(byte[] seed) {
|
||||
generator = new FortunaGenerator(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineGenerateSeed(int numBytes) {
|
||||
byte[] b = new byte[numBytes];
|
||||
engineNextBytes(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineNextBytes(byte[] b) {
|
||||
int offset = 0;
|
||||
while (offset < b.length)
|
||||
offset += generator.nextBytes(b, offset, b.length - offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineSetSeed(byte[] seed) {
|
||||
generator.reseed(seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FortunaProvider extends Provider {
|
||||
|
||||
private FortunaProvider() {
|
||||
super("Fortuna", 1.0, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,31 +11,14 @@ import javax.annotation.concurrent.Immutable;
|
||||
@NotNullByDefault
|
||||
class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
|
||||
|
||||
private static final int LOWER = 26;
|
||||
private static final int UPPER = 26;
|
||||
private static final int DIGIT = 10;
|
||||
private static final int OTHER = 10;
|
||||
private static final double STRONG = Math.log(Math.pow(LOWER + UPPER +
|
||||
DIGIT + OTHER, 10));
|
||||
// The minimum number of unique characters in a strong password
|
||||
private static final int STRONG_UNIQUE_CHARS = 12;
|
||||
|
||||
@Override
|
||||
public float estimateStrength(String password) {
|
||||
HashSet<Character> unique = new HashSet<Character>();
|
||||
HashSet<Character> unique = new HashSet<>();
|
||||
int length = password.length();
|
||||
for (int i = 0; i < length; i++) unique.add(password.charAt(i));
|
||||
boolean lower = false, upper = false, digit = false, other = false;
|
||||
for (char c : unique) {
|
||||
if (Character.isLowerCase(c)) lower = true;
|
||||
else if (Character.isUpperCase(c)) upper = true;
|
||||
else if (Character.isDigit(c)) digit = true;
|
||||
else other = true;
|
||||
}
|
||||
int alphabetSize = 0;
|
||||
if (lower) alphabetSize += LOWER;
|
||||
if (upper) alphabetSize += UPPER;
|
||||
if (digit) alphabetSize += DIGIT;
|
||||
if (other) alphabetSize += OTHER;
|
||||
double score = Math.log(Math.pow(alphabetSize, unique.size()));
|
||||
return Math.min(1, (float) (score / STRONG));
|
||||
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class PseudoRandomImpl implements PseudoRandom {
|
||||
|
||||
private final FortunaGenerator generator;
|
||||
|
||||
PseudoRandomImpl(int seed1, int seed2) {
|
||||
byte[] seed = new byte[INT_32_BYTES * 2];
|
||||
ByteUtils.writeUint32(seed1, seed, 0);
|
||||
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
|
||||
generator = new FortunaGenerator(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] nextBytes(int length) {
|
||||
byte[] b = new byte[length];
|
||||
int offset = 0;
|
||||
while (offset < length) offset += generator.nextBytes(b, offset, length);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||
public StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
|
||||
SecretKey headerKey) {
|
||||
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
|
||||
}
|
||||
|
||||
@@ -20,9 +20,11 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@@ -117,7 +119,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
|
||||
private void readStreamHeader() throws IOException {
|
||||
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
|
||||
byte[] streamHeaderPlaintext = new byte[SecretKey.LENGTH];
|
||||
byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
|
||||
// Read the stream header
|
||||
int offset = 0;
|
||||
while (offset < STREAM_HEADER_LENGTH) {
|
||||
@@ -126,21 +128,35 @@ class StreamDecrypterImpl implements StreamDecrypter {
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// The nonce consists of the stream number followed by the IV
|
||||
// Extract the nonce
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
|
||||
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce,
|
||||
INT_64_BYTES, STREAM_HEADER_IV_LENGTH);
|
||||
System.arraycopy(streamHeaderCiphertext, 0, streamHeaderNonce, 0,
|
||||
STREAM_HEADER_NONCE_LENGTH);
|
||||
// Decrypt and authenticate the stream header
|
||||
try {
|
||||
cipher.init(false, streamHeaderKey, streamHeaderNonce);
|
||||
int decrypted = cipher.process(streamHeaderCiphertext,
|
||||
STREAM_HEADER_IV_LENGTH, SecretKey.LENGTH + MAC_LENGTH,
|
||||
STREAM_HEADER_NONCE_LENGTH,
|
||||
STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH,
|
||||
streamHeaderPlaintext, 0);
|
||||
if (decrypted != SecretKey.LENGTH) throw new RuntimeException();
|
||||
if (decrypted != STREAM_HEADER_PLAINTEXT_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
frameKey = new SecretKey(streamHeaderPlaintext);
|
||||
// Check the protocol version
|
||||
int receivedProtocolVersion =
|
||||
ByteUtils.readUint16(streamHeaderPlaintext, 0);
|
||||
if (receivedProtocolVersion != PROTOCOL_VERSION)
|
||||
throw new FormatException();
|
||||
// Check the stream number
|
||||
long receivedStreamNumber = ByteUtils.readUint64(streamHeaderPlaintext,
|
||||
INT_16_BYTES);
|
||||
if (receivedStreamNumber != streamNumber) throw new FormatException();
|
||||
// Extract the frame key
|
||||
byte[] frameKeyBytes = new byte[SecretKey.LENGTH];
|
||||
System.arraycopy(streamHeaderPlaintext, INT_16_BYTES + INT_64_BYTES,
|
||||
frameKeyBytes, 0, SecretKey.LENGTH);
|
||||
frameKey = new SecretKey(frameKeyBytes);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
@Immutable
|
||||
@@ -36,22 +37,22 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
long streamNumber = ctx.getStreamNumber();
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, ctx.getTagKey(), streamNumber);
|
||||
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderIv);
|
||||
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||
SecretKey frameKey = crypto.generateSecretKey();
|
||||
return new StreamEncrypterImpl(out, cipher, streamNumber, tag,
|
||||
streamHeaderIv, ctx.getHeaderKey(), frameKey);
|
||||
streamHeaderNonce, ctx.getHeaderKey(), frameKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
SecretKey headerKey) {
|
||||
public StreamEncrypter createContactExchangeStreamDecrypter(
|
||||
OutputStream out, SecretKey headerKey) {
|
||||
AuthenticatedCipher cipher = cipherProvider.get();
|
||||
byte[] streamHeaderIv = new byte[STREAM_HEADER_IV_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderIv);
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||
SecretKey frameKey = crypto.generateSecretKey();
|
||||
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderIv,
|
||||
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
|
||||
headerKey, frameKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ import static org.briarproject.bramble.api.transport.TransportConstants.FRAME_NO
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_NONCE_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_PLAINTEXT_LENGTH;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||
|
||||
@NotThreadSafe
|
||||
@@ -33,7 +35,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
private final long streamNumber;
|
||||
@Nullable
|
||||
private final byte[] tag;
|
||||
private final byte[] streamHeaderIv;
|
||||
private final byte[] streamHeaderNonce;
|
||||
private final byte[] frameNonce, frameHeader;
|
||||
private final byte[] framePlaintext, frameCiphertext;
|
||||
|
||||
@@ -41,13 +43,13 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
private boolean writeTag, writeStreamHeader;
|
||||
|
||||
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher cipher,
|
||||
long streamNumber, @Nullable byte[] tag, byte[] streamHeaderIv,
|
||||
long streamNumber, @Nullable byte[] tag, byte[] streamHeaderNonce,
|
||||
SecretKey streamHeaderKey, SecretKey frameKey) {
|
||||
this.out = out;
|
||||
this.cipher = cipher;
|
||||
this.streamNumber = streamNumber;
|
||||
this.tag = tag;
|
||||
this.streamHeaderIv = streamHeaderIv;
|
||||
this.streamHeaderNonce = streamHeaderNonce;
|
||||
this.streamHeaderKey = streamHeaderKey;
|
||||
this.frameKey = frameKey;
|
||||
frameNonce = new byte[FRAME_NONCE_LENGTH];
|
||||
@@ -62,6 +64,8 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
@Override
|
||||
public void writeFrame(byte[] payload, int payloadLength,
|
||||
int paddingLength, boolean finalFrame) throws IOException {
|
||||
if (payloadLength < 0 || paddingLength < 0)
|
||||
throw new IllegalArgumentException();
|
||||
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
// Don't allow the frame counter to wrap
|
||||
@@ -112,22 +116,23 @@ class StreamEncrypterImpl implements StreamEncrypter {
|
||||
}
|
||||
|
||||
private void writeStreamHeader() throws IOException {
|
||||
// The nonce consists of the stream number followed by the IV
|
||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||
ByteUtils.writeUint64(streamNumber, streamHeaderNonce, 0);
|
||||
System.arraycopy(streamHeaderIv, 0, streamHeaderNonce, INT_64_BYTES,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
byte[] streamHeaderPlaintext = frameKey.getBytes();
|
||||
// The header contains the protocol version, stream number and frame key
|
||||
byte[] streamHeaderPlaintext = new byte[STREAM_HEADER_PLAINTEXT_LENGTH];
|
||||
ByteUtils.writeUint16(PROTOCOL_VERSION, streamHeaderPlaintext, 0);
|
||||
ByteUtils.writeUint64(streamNumber, streamHeaderPlaintext,
|
||||
INT_16_BYTES);
|
||||
System.arraycopy(frameKey.getBytes(), 0, streamHeaderPlaintext,
|
||||
INT_16_BYTES + INT_64_BYTES, SecretKey.LENGTH);
|
||||
byte[] streamHeaderCiphertext = new byte[STREAM_HEADER_LENGTH];
|
||||
System.arraycopy(streamHeaderIv, 0, streamHeaderCiphertext, 0,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
// Encrypt and authenticate the frame key
|
||||
System.arraycopy(streamHeaderNonce, 0, streamHeaderCiphertext, 0,
|
||||
STREAM_HEADER_NONCE_LENGTH);
|
||||
// Encrypt and authenticate the stream header key
|
||||
try {
|
||||
cipher.init(true, streamHeaderKey, streamHeaderNonce);
|
||||
int encrypted = cipher.process(streamHeaderPlaintext, 0,
|
||||
SecretKey.LENGTH, streamHeaderCiphertext,
|
||||
STREAM_HEADER_IV_LENGTH);
|
||||
if (encrypted != SecretKey.LENGTH + MAC_LENGTH)
|
||||
STREAM_HEADER_PLAINTEXT_LENGTH, streamHeaderCiphertext,
|
||||
STREAM_HEADER_NONCE_LENGTH);
|
||||
if (encrypted != STREAM_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch (GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
|
||||
@@ -70,25 +70,7 @@ class XSalsa20Poly1305AuthenticatedCipher implements AuthenticatedCipher {
|
||||
byte[] subKey = new byte[SUBKEY_LENGTH];
|
||||
xSalsa20Engine.processBytes(zero, 0, SUBKEY_LENGTH, subKey, 0);
|
||||
|
||||
// Reverse the order of the Poly130 subkey
|
||||
//
|
||||
// NaCl and libsodium use the first 32 bytes of XSalsa20 as the
|
||||
// subkey for crypto_onetimeauth_poly1305, which interprets it
|
||||
// as r[0] ... r[15], k[0] ... k[15]. See section 9 of the NaCl
|
||||
// paper (http://cr.yp.to/highspeed/naclcrypto-20090310.pdf),
|
||||
// where the XSalsa20 output is defined as (r, s, t, ...).
|
||||
//
|
||||
// BC's Poly1305 implementation interprets the subkey as
|
||||
// k[0] ... k[15], r[0] ... r[15] (per poly1305_aes_clamp in
|
||||
// the reference implementation).
|
||||
//
|
||||
// To be NaCl-compatible, we reverse the subkey.
|
||||
System.arraycopy(subKey, 0, zero, 0, SUBKEY_LENGTH / 2);
|
||||
System.arraycopy(subKey, SUBKEY_LENGTH / 2, subKey, 0,
|
||||
SUBKEY_LENGTH / 2);
|
||||
System.arraycopy(zero, 0, subKey, SUBKEY_LENGTH / 2,
|
||||
SUBKEY_LENGTH / 2);
|
||||
// Now we can clamp the correct part of the subkey
|
||||
// Clamp the subkey
|
||||
Poly1305KeyGenerator.clamp(subKey);
|
||||
|
||||
// Initialize Poly1305 with the subkey
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
@@ -37,6 +39,11 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Opens the database and returns true if the database already existed.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open() throws DbException;
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.FINE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@@ -89,8 +90,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
private final ReentrantReadWriteLock lock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
|
||||
private volatile int shutdownHandle = -1;
|
||||
|
||||
@Inject
|
||||
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
||||
ShutdownManager shutdown) {
|
||||
@@ -102,26 +101,20 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
|
||||
@Override
|
||||
public boolean open() throws DbException {
|
||||
Runnable shutdownHook = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
close();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
boolean reopened = db.open();
|
||||
shutdownHandle = shutdown.addShutdownHook(shutdownHook);
|
||||
shutdown.addShutdownHook(() -> {
|
||||
try {
|
||||
close();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
});
|
||||
return reopened;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws DbException {
|
||||
if (closed.getAndSet(true)) return;
|
||||
shutdown.removeShutdownHook(shutdownHandle);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@@ -130,15 +123,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
// Don't allow reentrant locking
|
||||
if (lock.getReadHoldCount() > 0) throw new IllegalStateException();
|
||||
if (lock.getWriteHoldCount() > 0) throw new IllegalStateException();
|
||||
long start = System.currentTimeMillis();
|
||||
if (readOnly) lock.readLock().lock();
|
||||
else lock.writeLock().lock();
|
||||
if (LOG.isLoggable(FINE)) {
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
if (readOnly) LOG.fine("Waited " + duration + " ms for read lock");
|
||||
else LOG.fine("Waited " + duration + " ms for write lock");
|
||||
}
|
||||
try {
|
||||
return new Transaction(db.startTransaction(), readOnly);
|
||||
} catch (DbException e) {
|
||||
if (readOnly) lock.readLock().unlock();
|
||||
else lock.writeLock().unlock();
|
||||
throw e;
|
||||
} catch (RuntimeException e) {
|
||||
} catch (DbException | RuntimeException e) {
|
||||
if (readOnly) lock.readLock().unlock();
|
||||
else lock.writeLock().unlock();
|
||||
throw e;
|
||||
@@ -324,7 +319,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, c, maxLength);
|
||||
List<byte[]> messages = new ArrayList<byte[]>(ids.size());
|
||||
List<byte[]> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getRawMessage(txn, m));
|
||||
db.updateExpiryTime(txn, c, m, maxLatency);
|
||||
@@ -374,7 +369,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids = db.getRequestedMessagesToSend(txn, c,
|
||||
maxLength);
|
||||
List<byte[]> messages = new ArrayList<byte[]>(ids.size());
|
||||
List<byte[]> messages = new ArrayList<>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getRawMessage(txn, m));
|
||||
db.updateExpiryTime(txn, c, m, maxLatency);
|
||||
@@ -654,14 +649,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> acked = new ArrayList<MessageId>();
|
||||
Collection<MessageId> acked = new ArrayList<>();
|
||||
for (MessageId m : a.getMessageIds()) {
|
||||
if (db.containsVisibleMessage(txn, c, m)) {
|
||||
db.raiseSeenFlag(txn, c, m);
|
||||
acked.add(m);
|
||||
}
|
||||
}
|
||||
transaction.attach(new MessagesAckedEvent(c, acked));
|
||||
if (acked.size() > 0) {
|
||||
transaction.attach(new MessagesAckedEvent(c, acked));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -761,6 +758,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
transaction.attach(new LocalAuthorRemovedEvent(a));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMessage(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.removeMessage(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransport(Transaction transaction, TransportId t)
|
||||
throws DbException {
|
||||
@@ -877,8 +884,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
Map<ContactId, TransportKeys> filtered =
|
||||
new HashMap<ContactId, TransportKeys>();
|
||||
Map<ContactId, TransportKeys> filtered = new HashMap<>();
|
||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
TransportKeys k = e.getValue();
|
||||
|
||||
@@ -23,10 +23,4 @@ interface DatabaseConstants {
|
||||
*/
|
||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the minimum supported database
|
||||
* schema version is stored.
|
||||
*/
|
||||
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
|
||||
@@ -31,13 +32,13 @@ public class DatabaseExecutorModule {
|
||||
|
||||
public DatabaseExecutorModule() {
|
||||
// Use an unbounded queue
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
// Use a single thread and keep it in the pool for 60 secs
|
||||
databaseExecutor = new ThreadPoolExecutor(0, 1, 60, SECONDS, queue,
|
||||
policy);
|
||||
databaseExecutor = new TimeLoggingExecutor("DatabaseExecutor", 0, 1,
|
||||
60, SECONDS, queue, policy);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -26,7 +26,7 @@ public class DatabaseModule {
|
||||
@Singleton
|
||||
DatabaseComponent provideDatabaseComponent(Database<Connection> db,
|
||||
EventBus eventBus, ShutdownManager shutdown) {
|
||||
return new DatabaseComponentImpl<Connection>(db, Connection.class,
|
||||
eventBus, shutdown);
|
||||
return new DatabaseComponentImpl<>(db, Connection.class, eventBus,
|
||||
shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbClosedException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
@@ -47,6 +49,7 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
@@ -57,7 +60,6 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MIN_SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
|
||||
@@ -68,8 +70,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
@NotNullByDefault
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
private static final int SCHEMA_VERSION = 29;
|
||||
private static final int MIN_SCHEMA_VERSION = 29;
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 31;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -148,11 +150,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private static final String CREATE_MESSAGE_METADATA =
|
||||
"CREATE TABLE messageMetadata"
|
||||
+ " (messageId HASH NOT NULL,"
|
||||
+ " groupId HASH NOT NULL," // Denormalised
|
||||
+ " state INT NOT NULL," // Denormalised
|
||||
+ " key VARCHAR NOT NULL,"
|
||||
+ " value BINARY NOT NULL,"
|
||||
+ " PRIMARY KEY (messageId, key),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
||||
@@ -232,6 +239,18 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String INDEX_CONTACTS_BY_AUTHOR_ID =
|
||||
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
||||
+ " ON contacts (authorId)";
|
||||
|
||||
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
||||
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
||||
+ " ON groups (clientId)";
|
||||
|
||||
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||
+ " ON messageMetadata (groupId, state)";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JdbcDatabase.class.getName());
|
||||
|
||||
@@ -239,8 +258,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private final String hashType, binaryType, counterType, secretType;
|
||||
private final Clock clock;
|
||||
|
||||
private final LinkedList<Connection> connections =
|
||||
new LinkedList<Connection>(); // Locking: connectionsLock
|
||||
// Locking: connectionsLock
|
||||
private final LinkedList<Connection> connections = new LinkedList<>();
|
||||
|
||||
private int openConnections = 0; // Locking: connectionsLock
|
||||
private boolean closed = false; // Locking: connectionsLock
|
||||
@@ -267,15 +286,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
// Open the database and create the tables if necessary
|
||||
// Open the database and create the tables and indexes if necessary
|
||||
Connection txn = startTransaction();
|
||||
try {
|
||||
if (reopen) {
|
||||
if (!checkSchemaVersion(txn)) throw new DbException();
|
||||
checkSchemaVersion(txn);
|
||||
} else {
|
||||
createTables(txn);
|
||||
storeSchemaVersion(txn);
|
||||
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
|
||||
}
|
||||
createIndexes(txn);
|
||||
commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
abortTransaction(txn);
|
||||
@@ -283,19 +303,49 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkSchemaVersion(Connection txn) throws DbException {
|
||||
/**
|
||||
* Compares the schema version stored in the database with the schema
|
||||
* version used by the current code and applies any suitable migrations to
|
||||
* the data if necessary.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
private void checkSchemaVersion(Connection txn) throws DbException {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
int schemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
if (schemaVersion == SCHEMA_VERSION) return true;
|
||||
if (schemaVersion < MIN_SCHEMA_VERSION) return false;
|
||||
int minSchemaVersion = s.getInt(MIN_SCHEMA_VERSION_KEY, -1);
|
||||
return SCHEMA_VERSION >= minSchemaVersion;
|
||||
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
if (dataSchemaVersion == -1) throw new DbException();
|
||||
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return;
|
||||
if (CODE_SCHEMA_VERSION < dataSchemaVersion)
|
||||
throw new DataTooNewException();
|
||||
// Apply any suitable migrations in order
|
||||
for (Migration<Connection> m : getMigrations()) {
|
||||
int start = m.getStartVersion(), end = m.getEndVersion();
|
||||
if (start == dataSchemaVersion) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Migrating from schema " + start + " to " + end);
|
||||
// Apply the migration
|
||||
m.migrate(txn);
|
||||
// Store the new schema version
|
||||
storeSchemaVersion(txn, end);
|
||||
dataSchemaVersion = end;
|
||||
}
|
||||
}
|
||||
if (dataSchemaVersion != CODE_SCHEMA_VERSION)
|
||||
throw new DataTooOldException();
|
||||
}
|
||||
|
||||
private void storeSchemaVersion(Connection txn) throws DbException {
|
||||
// Package access for testing
|
||||
List<Migration<Connection>> getMigrations() {
|
||||
return Collections.singletonList(new Migration30_31());
|
||||
}
|
||||
|
||||
private void storeSchemaVersion(Connection txn, int version)
|
||||
throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
|
||||
s.putInt(MIN_SCHEMA_VERSION_KEY, MIN_SCHEMA_VERSION);
|
||||
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
@@ -340,6 +390,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private void createIndexes(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String insertTypeNames(String s) {
|
||||
s = s.replaceAll("HASH", hashType);
|
||||
s = s.replaceAll("BINARY", binaryType);
|
||||
@@ -458,7 +522,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
// Create a contact row
|
||||
String sql = "INSERT INTO contacts"
|
||||
+ " (authorId, name, publicKey, localAuthorId, verified, active)"
|
||||
+ " (authorId, name, publicKey, localAuthorId,"
|
||||
+ " verified, active)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, remote.getId().getBytes());
|
||||
@@ -993,7 +1058,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " FROM contacts";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
List<Contact> contacts = new ArrayList<Contact>();
|
||||
List<Contact> contacts = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
ContactId contactId = new ContactId(rs.getInt(1));
|
||||
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||
@@ -1027,7 +1092,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, local.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<ContactId> ids = new ArrayList<ContactId>();
|
||||
List<ContactId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new ContactId(rs.getInt(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1052,7 +1117,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, remote.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<Contact> contacts = new ArrayList<Contact>();
|
||||
List<Contact> contacts = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
ContactId c = new ContactId(rs.getInt(1));
|
||||
String name = rs.getString(2);
|
||||
@@ -1108,7 +1173,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
List<Group> groups = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
GroupId id = new GroupId(rs.getBytes(1));
|
||||
byte[] descriptor = rs.getBytes(2);
|
||||
@@ -1161,7 +1226,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<ContactId> visible = new ArrayList<ContactId>();
|
||||
List<ContactId> visible = new ArrayList<>();
|
||||
while (rs.next()) visible.add(new ContactId(rs.getInt(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1213,7 +1278,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " FROM localAuthors";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
List<LocalAuthor> authors = new ArrayList<LocalAuthor>();
|
||||
List<LocalAuthor> authors = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
||||
String name = rs.getString(2);
|
||||
@@ -1243,7 +1308,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1266,7 +1331,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1288,20 +1353,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
// Retrieve the message IDs for each query term and intersect
|
||||
Set<MessageId> intersection = null;
|
||||
String sql = "SELECT m.messageId"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN messageMetadata AS md"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE state = ? AND groupId = ?"
|
||||
String sql = "SELECT messageId FROM messageMetadata"
|
||||
+ " WHERE groupId = ? AND state = ?"
|
||||
+ " AND key = ? AND value = ?";
|
||||
for (Entry<String, byte[]> e : query.entrySet()) {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setString(3, e.getKey());
|
||||
ps.setBytes(4, e.getValue());
|
||||
rs = ps.executeQuery();
|
||||
Set<MessageId> ids = new HashSet<MessageId>();
|
||||
Set<MessageId> ids = new HashSet<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1325,25 +1387,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT m.messageId, key, value"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN messageMetadata AS md"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE state = ? AND groupId = ?"
|
||||
+ " ORDER BY m.messageId";
|
||||
String sql = "SELECT messageId, key, value"
|
||||
+ " FROM messageMetadata"
|
||||
+ " WHERE groupId = ? AND state = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, Metadata> all = new HashMap<MessageId, Metadata>();
|
||||
Metadata metadata = null;
|
||||
MessageId lastMessageId = null;
|
||||
Map<MessageId, Metadata> all = new HashMap<>();
|
||||
while (rs.next()) {
|
||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||
if (lastMessageId == null || !messageId.equals(lastMessageId)) {
|
||||
Metadata metadata = all.get(messageId);
|
||||
if (metadata == null) {
|
||||
metadata = new Metadata();
|
||||
all.put(messageId, metadata);
|
||||
lastMessageId = messageId;
|
||||
}
|
||||
metadata.put(rs.getString(2), rs.getBytes(3));
|
||||
}
|
||||
@@ -1364,8 +1421,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
Collection<MessageId> matches = getMessageIds(txn, g, query);
|
||||
if (matches.isEmpty()) return Collections.emptyMap();
|
||||
// Retrieve the metadata for each match
|
||||
Map<MessageId, Metadata> all = new HashMap<MessageId, Metadata>(
|
||||
matches.size());
|
||||
Map<MessageId, Metadata> all = new HashMap<>(matches.size());
|
||||
for (MessageId m : matches) all.put(m, getMessageMetadata(txn, m));
|
||||
return all;
|
||||
}
|
||||
@@ -1399,10 +1455,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE m.state = ? AND md.messageId = ?";
|
||||
String sql = "SELECT key, value FROM messageMetadata"
|
||||
+ " WHERE state = ? AND messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
@@ -1425,11 +1479,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE (m.state = ? OR m.state = ?)"
|
||||
+ " AND md.messageId = ?";
|
||||
String sql = "SELECT key, value FROM messageMetadata"
|
||||
+ " WHERE (state = ? OR state = ?)"
|
||||
+ " AND messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setInt(2, PENDING.getValue());
|
||||
@@ -1463,7 +1515,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageStatus> statuses = new ArrayList<MessageStatus>();
|
||||
List<MessageStatus> statuses = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||
boolean sent = rs.getBoolean(2);
|
||||
@@ -1522,7 +1574,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, State> dependencies = new HashMap<MessageId, State>();
|
||||
Map<MessageId, State> dependencies = new HashMap<>();
|
||||
while (rs.next()) {
|
||||
MessageId dependency = new MessageId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
@@ -1560,7 +1612,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, State> dependents = new HashMap<MessageId, State>();
|
||||
Map<MessageId, State> dependents = new HashMap<>();
|
||||
while (rs.next()) {
|
||||
MessageId dependent = new MessageId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
@@ -1612,7 +1664,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1648,7 +1700,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setLong(3, now);
|
||||
ps.setInt(4, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1673,7 +1725,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1708,7 +1760,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
while (rs.next()) {
|
||||
int length = rs.getInt(1);
|
||||
@@ -1750,7 +1802,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setString(2, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1780,7 +1832,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1839,7 +1891,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
int total = 0;
|
||||
while (rs.next()) {
|
||||
int length = rs.getInt(1);
|
||||
@@ -1893,7 +1945,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<IncomingKeys> inKeys = new ArrayList<IncomingKeys>();
|
||||
List<IncomingKeys> inKeys = new ArrayList<>();
|
||||
while (rs.next()) {
|
||||
long rotationPeriod = rs.getLong(1);
|
||||
SecretKey tagKey = new SecretKey(rs.getBytes(2));
|
||||
@@ -1913,8 +1965,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
Map<ContactId, TransportKeys> keys =
|
||||
new HashMap<ContactId, TransportKeys>();
|
||||
Map<ContactId, TransportKeys> keys = new HashMap<>();
|
||||
for (int i = 0; rs.next(); i++) {
|
||||
// There should be three times as many incoming keys
|
||||
if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
|
||||
@@ -2004,7 +2055,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != requested.size())
|
||||
throw new DbStateException();
|
||||
for (int rows: batchAffected) {
|
||||
for (int rows : batchAffected) {
|
||||
if (rows < 0) throw new DbStateException();
|
||||
if (rows > 1) throw new DbStateException();
|
||||
}
|
||||
@@ -2018,25 +2069,92 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
@Override
|
||||
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
|
||||
throws DbException {
|
||||
mergeMetadata(txn, g.getBytes(), meta, "groupMetadata", "groupId");
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||
g.getBytes(), meta, "groupMetadata", "groupId");
|
||||
if (added.isEmpty()) return;
|
||||
// Insert any keys that don't already exist
|
||||
String sql = "INSERT INTO groupMetadata (groupId, key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setBytes(3, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != added.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeMessageMetadata(Connection txn, MessageId m, Metadata meta)
|
||||
throws DbException {
|
||||
mergeMetadata(txn, m.getBytes(), meta, "messageMetadata", "messageId");
|
||||
public void mergeMessageMetadata(Connection txn, MessageId m,
|
||||
Metadata meta) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||
m.getBytes(), meta, "messageMetadata", "messageId");
|
||||
if (added.isEmpty()) return;
|
||||
// Get the group ID and message state for the denormalised columns
|
||||
String sql = "SELECT groupId, state FROM messages"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId g = new GroupId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Insert any keys that don't already exist
|
||||
sql = "INSERT INTO messageMetadata"
|
||||
+ " (messageId, groupId, state, key, value)"
|
||||
+ " VALUES (?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setInt(3, state.getValue());
|
||||
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||
ps.setString(4, e.getKey());
|
||||
ps.setBytes(5, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != added.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeMetadata(Connection txn, byte[] id, Metadata meta,
|
||||
String tableName, String columnName) throws DbException {
|
||||
// Removes or updates any existing entries, returns any entries that
|
||||
// need to be added
|
||||
private Map<String, byte[]> removeOrUpdateMetadata(Connection txn,
|
||||
byte[] id, Metadata meta, String tableName, String columnName)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Determine which keys are being removed
|
||||
List<String> removed = new ArrayList<String>();
|
||||
Map<String, byte[]> retained = new HashMap<String, byte[]>();
|
||||
List<String> removed = new ArrayList<>();
|
||||
Map<String, byte[]> notRemoved = new HashMap<>();
|
||||
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||
if (e.getValue() == REMOVE) removed.add(e.getKey());
|
||||
else retained.put(e.getKey(), e.getValue());
|
||||
else notRemoved.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// Delete any keys that are being removed
|
||||
if (!removed.isEmpty()) {
|
||||
@@ -2057,45 +2175,33 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
ps.close();
|
||||
}
|
||||
if (retained.isEmpty()) return;
|
||||
if (notRemoved.isEmpty()) return Collections.emptyMap();
|
||||
// Update any keys that already exist
|
||||
String sql = "UPDATE " + tableName + " SET value = ?"
|
||||
+ " WHERE " + columnName + " = ? AND key = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(2, id);
|
||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
||||
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||
ps.setBytes(1, e.getValue());
|
||||
ps.setString(3, e.getKey());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != retained.size())
|
||||
if (batchAffected.length != notRemoved.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected) {
|
||||
if (rows < 0) throw new DbStateException();
|
||||
if (rows > 1) throw new DbStateException();
|
||||
}
|
||||
// Insert any keys that don't already exist
|
||||
sql = "INSERT INTO " + tableName
|
||||
+ " (" + columnName + ", key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, id);
|
||||
int updateIndex = 0, inserted = 0;
|
||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
||||
if (batchAffected[updateIndex] == 0) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setBytes(3, e.getValue());
|
||||
ps.addBatch();
|
||||
inserted++;
|
||||
}
|
||||
updateIndex++;
|
||||
}
|
||||
batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != inserted) throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Are there any keys that don't already exist?
|
||||
Map<String, byte[]> added = new HashMap<>();
|
||||
int updateIndex = 0;
|
||||
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||
if (batchAffected[updateIndex++] == 0)
|
||||
added.put(e.getKey(), e.getValue());
|
||||
}
|
||||
return added;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2448,7 +2554,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageShared(Connection txn, MessageId m) throws DbException {
|
||||
public void setMessageShared(Connection txn, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE messages SET shared = TRUE"
|
||||
@@ -2476,6 +2583,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in messageMetadata
|
||||
sql = "UPDATE messageMetadata SET state = ? WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
interface Migration<T> {
|
||||
|
||||
/**
|
||||
* Returns the schema version from which this migration starts.
|
||||
*/
|
||||
int getStartVersion();
|
||||
|
||||
/**
|
||||
* Returns the schema version at which this migration ends.
|
||||
*/
|
||||
int getEndVersion();
|
||||
|
||||
void migrate(T txn) throws DbException;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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 javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
class Migration30_31 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Migration30_31.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 31;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
// Add groupId column
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD COLUMN groupId BINARY(32) AFTER messageId");
|
||||
// Populate groupId column
|
||||
s.execute("UPDATE messageMetadata AS mm SET groupId ="
|
||||
+ " (SELECT groupId FROM messages AS m"
|
||||
+ " WHERE mm.messageId = m.messageId)");
|
||||
// Add not null constraint now column has been populated
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ALTER COLUMN groupId"
|
||||
+ " SET NOT NULL");
|
||||
// Add foreign key constraint
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD CONSTRAINT groupIdForeignKey"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE");
|
||||
// Add state column
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD COLUMN state INT AFTER groupId");
|
||||
// Populate state column
|
||||
s.execute("UPDATE messageMetadata AS mm SET state ="
|
||||
+ " (SELECT state FROM messages AS m"
|
||||
+ " WHERE mm.messageId = m.messageId)");
|
||||
// Add not null constraint now column has been populated
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ALTER COLUMN state"
|
||||
+ " SET NOT NULL");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Statement s) {
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
} catch (SQLException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
class EventBusImpl implements EventBus {
|
||||
|
||||
private final Collection<EventListener> listeners =
|
||||
new CopyOnWriteArrayList<EventListener>();
|
||||
new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public void addListener(EventListener l) {
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* A connection thread for the peer being Alice in the invitation protocol.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class AliceConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AliceConnector.class.getName());
|
||||
|
||||
AliceConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
||||
group, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Create an incoming or outgoing connection
|
||||
DuplexTransportConnection conn = createInvitationConnection(true);
|
||||
if (conn == null) return;
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// Don't proceed with more than one connection
|
||||
if (group.getAndSetConnected()) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
BdfReader r;
|
||||
BdfWriter w;
|
||||
SecretKey master;
|
||||
try {
|
||||
in = conn.getReader().getInputStream();
|
||||
out = conn.getWriter().getOutputStream();
|
||||
r = bdfReaderFactory.createReader(in);
|
||||
w = bdfWriterFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
sendPublicKeyHash(w);
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
sendPublicKey(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
master = deriveMasterSecret(hash, key, true);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
||||
group.keyAgreementSucceeded(aliceCode, bobCode);
|
||||
// Exchange confirmation results
|
||||
boolean localMatched, remoteMatched;
|
||||
try {
|
||||
localMatched = group.waitForLocalConfirmationResult();
|
||||
sendConfirmation(w, localMatched);
|
||||
remoteMatched = receiveConfirmation(r);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
if (!(localMatched && remoteMatched)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation failed");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Confirmation succeeded - upgrade to a secure connection
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
||||
plugin.getId(), true);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
/**
|
||||
* A connection thread for the peer being Bob in the invitation protocol.
|
||||
*/
|
||||
@NotNullByDefault
|
||||
class BobConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BobConnector.class.getName());
|
||||
|
||||
BobConnector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super(crypto, bdfReaderFactory, bdfWriterFactory, contactExchangeTask,
|
||||
group, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Create an incoming or outgoing connection
|
||||
DuplexTransportConnection conn = createInvitationConnection(false);
|
||||
if (conn == null) return;
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
BdfReader r;
|
||||
BdfWriter w;
|
||||
SecretKey master;
|
||||
try {
|
||||
in = conn.getReader().getInputStream();
|
||||
out = conn.getWriter().getOutputStream();
|
||||
r = bdfReaderFactory.createReader(in);
|
||||
w = bdfWriterFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
// Don't proceed with more than one connection
|
||||
if (group.getAndSetConnected()) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
sendPublicKeyHash(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
sendPublicKey(w);
|
||||
master = deriveMasterSecret(hash, key, false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.keyAgreementFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded - derive the confirmation codes
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
|
||||
int aliceCode = crypto.deriveBTConfirmationCode(master, true);
|
||||
int bobCode = crypto.deriveBTConfirmationCode(master, false);
|
||||
group.keyAgreementSucceeded(bobCode, aliceCode);
|
||||
// Exchange confirmation results
|
||||
boolean localMatched, remoteMatched;
|
||||
try {
|
||||
remoteMatched = receiveConfirmation(r);
|
||||
localMatched = group.waitForLocalConfirmationResult();
|
||||
sendConfirmation(w, localMatched);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for confirmation");
|
||||
group.remoteConfirmationFailed();
|
||||
tryToClose(conn, true);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
if (remoteMatched) group.remoteConfirmationSucceeded();
|
||||
else group.remoteConfirmationFailed();
|
||||
if (!(localMatched && remoteMatched)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation failed");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// Confirmation succeeded - upgrade to a secure connection
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
contactExchangeTask.startExchange(group, localAuthor, master, conn,
|
||||
plugin.getId(), false);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfReader;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriter;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
// FIXME: This class has way too many dependencies
|
||||
@NotNullByDefault
|
||||
abstract class Connector extends Thread {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Connector.class.getName());
|
||||
private static final String LABEL_PUBLIC_KEY =
|
||||
"org.briarproject.bramble.invitation.PUBLIC_KEY";
|
||||
|
||||
protected final CryptoComponent crypto;
|
||||
protected final BdfReaderFactory bdfReaderFactory;
|
||||
protected final BdfWriterFactory bdfWriterFactory;
|
||||
protected final ContactExchangeTask contactExchangeTask;
|
||||
protected final ConnectorGroup group;
|
||||
protected final DuplexPlugin plugin;
|
||||
protected final LocalAuthor localAuthor;
|
||||
protected final PseudoRandom random;
|
||||
protected final String pluginName;
|
||||
|
||||
private final KeyPair keyPair;
|
||||
private final KeyParser keyParser;
|
||||
|
||||
Connector(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask, ConnectorGroup group,
|
||||
DuplexPlugin plugin, LocalAuthor localAuthor, PseudoRandom random) {
|
||||
super("Connector");
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.group = group;
|
||||
this.plugin = plugin;
|
||||
this.localAuthor = localAuthor;
|
||||
this.random = random;
|
||||
pluginName = plugin.getClass().getName();
|
||||
keyPair = crypto.generateAgreementKeyPair();
|
||||
keyParser = crypto.getAgreementKeyParser();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
DuplexTransportConnection createInvitationConnection(boolean alice) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " creating invitation connection");
|
||||
return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT,
|
||||
alice);
|
||||
}
|
||||
|
||||
void sendPublicKeyHash(BdfWriter w) throws IOException {
|
||||
byte[] hash =
|
||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
w.writeRaw(hash);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
||||
}
|
||||
|
||||
byte[] receivePublicKeyHash(BdfReader r) throws IOException {
|
||||
int hashLength = crypto.getHashLength();
|
||||
byte[] b = r.readRaw(hashLength);
|
||||
if (b.length < hashLength) throw new FormatException();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
return b;
|
||||
}
|
||||
|
||||
void sendPublicKey(BdfWriter w) throws IOException {
|
||||
byte[] key = keyPair.getPublic().getEncoded();
|
||||
w.writeRaw(key);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
|
||||
}
|
||||
|
||||
byte[] receivePublicKey(BdfReader r)
|
||||
throws GeneralSecurityException, IOException {
|
||||
byte[] b = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
||||
keyParser.parsePublicKey(b);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
|
||||
return b;
|
||||
}
|
||||
|
||||
SecretKey deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
|
||||
throws GeneralSecurityException {
|
||||
// Check that the hash matches the key
|
||||
byte[] keyHash =
|
||||
crypto.hash(LABEL_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
if (!Arrays.equals(hash, keyHash)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " hash does not match key");
|
||||
throw new GeneralSecurityException();
|
||||
}
|
||||
// Derive the master secret
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " deriving master secret");
|
||||
return crypto.deriveMasterSecret(key, keyPair, alice);
|
||||
}
|
||||
|
||||
void sendConfirmation(BdfWriter w, boolean confirmed) throws IOException {
|
||||
w.writeBoolean(confirmed);
|
||||
w.flush();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " sent confirmation: " + confirmed);
|
||||
}
|
||||
|
||||
boolean receiveConfirmation(BdfReader r) throws IOException {
|
||||
boolean confirmed = r.readBoolean();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " received confirmation: " + confirmed);
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
protected void tryToClose(DuplexTransportConnection conn,
|
||||
boolean exception) {
|
||||
try {
|
||||
LOG.info("Closing connection");
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeListener;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PseudoRandom;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.invitation.InvitationListener;
|
||||
import org.briarproject.bramble.api.invitation.InvitationState;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
|
||||
|
||||
/**
|
||||
* A task consisting of one or more parallel connection attempts.
|
||||
*/
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class ConnectorGroup extends Thread implements InvitationTask,
|
||||
ContactExchangeListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectorGroup.class.getName());
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final ContactExchangeTask contactExchangeTask;
|
||||
private final IdentityManager identityManager;
|
||||
private final PluginManager pluginManager;
|
||||
private final int localInvitationCode, remoteInvitationCode;
|
||||
private final Collection<InvitationListener> listeners;
|
||||
private final AtomicBoolean connected;
|
||||
private final CountDownLatch localConfirmationLatch;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
|
||||
private boolean connectionFailed = false;
|
||||
private boolean localCompared = false, remoteCompared = false;
|
||||
private boolean localMatched = false, remoteMatched = false;
|
||||
private String remoteName = null;
|
||||
|
||||
ConnectorGroup(CryptoComponent crypto, BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask,
|
||||
IdentityManager identityManager, PluginManager pluginManager,
|
||||
int localInvitationCode, int remoteInvitationCode) {
|
||||
super("ConnectorGroup");
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.identityManager = identityManager;
|
||||
this.pluginManager = pluginManager;
|
||||
this.localInvitationCode = localInvitationCode;
|
||||
this.remoteInvitationCode = remoteInvitationCode;
|
||||
listeners = new CopyOnWriteArrayList<InvitationListener>();
|
||||
connected = new AtomicBoolean(false);
|
||||
localConfirmationLatch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvitationState addListener(InvitationListener l) {
|
||||
lock.lock();
|
||||
try {
|
||||
listeners.add(l);
|
||||
return new InvitationState(localInvitationCode,
|
||||
remoteInvitationCode, localConfirmationCode,
|
||||
remoteConfirmationCode, connected.get(), connectionFailed,
|
||||
localCompared, remoteCompared, localMatched, remoteMatched,
|
||||
remoteName);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvitationListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LocalAuthor localAuthor;
|
||||
// Load the local pseudonym
|
||||
try {
|
||||
localAuthor = identityManager.getLocalAuthor();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
lock.lock();
|
||||
try {
|
||||
connectionFailed = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.connectionFailed();
|
||||
return;
|
||||
}
|
||||
// Start the connection threads
|
||||
Collection<Connector> connectors = new ArrayList<Connector>();
|
||||
// Alice is the party with the smaller invitation code
|
||||
if (localInvitationCode < remoteInvitationCode) {
|
||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
||||
Connector c = createAliceConnector(plugin, localAuthor);
|
||||
connectors.add(c);
|
||||
c.start();
|
||||
}
|
||||
} else {
|
||||
for (DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
|
||||
Connector c = createBobConnector(plugin, localAuthor);
|
||||
connectors.add(c);
|
||||
c.start();
|
||||
}
|
||||
}
|
||||
// Wait for the connection threads to finish
|
||||
try {
|
||||
for (Connector c : connectors) c.join();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting for connectors");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
// If none of the threads connected, inform the listeners
|
||||
if (!connected.get()) {
|
||||
lock.lock();
|
||||
try {
|
||||
connectionFailed = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.connectionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private Connector createAliceConnector(DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor) {
|
||||
PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
|
||||
remoteInvitationCode);
|
||||
return new AliceConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, this, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
private Connector createBobConnector(DuplexPlugin plugin,
|
||||
LocalAuthor localAuthor) {
|
||||
PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
|
||||
localInvitationCode);
|
||||
return new BobConnector(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, this, plugin, localAuthor, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localConfirmationSucceeded() {
|
||||
lock.lock();
|
||||
try {
|
||||
localCompared = true;
|
||||
localMatched = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void localConfirmationFailed() {
|
||||
lock.lock();
|
||||
try {
|
||||
localCompared = true;
|
||||
localMatched = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
localConfirmationLatch.countDown();
|
||||
}
|
||||
|
||||
boolean getAndSetConnected() {
|
||||
boolean redundant = connected.getAndSet(true);
|
||||
if (!redundant)
|
||||
for (InvitationListener l : listeners) l.connectionSucceeded();
|
||||
return redundant;
|
||||
}
|
||||
|
||||
void keyAgreementSucceeded(int localCode, int remoteCode) {
|
||||
lock.lock();
|
||||
try {
|
||||
localConfirmationCode = localCode;
|
||||
remoteConfirmationCode = remoteCode;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners)
|
||||
l.keyAgreementSucceeded(localCode, remoteCode);
|
||||
}
|
||||
|
||||
void keyAgreementFailed() {
|
||||
for (InvitationListener l : listeners) l.keyAgreementFailed();
|
||||
}
|
||||
|
||||
boolean waitForLocalConfirmationResult() throws InterruptedException {
|
||||
localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
|
||||
lock.lock();
|
||||
try {
|
||||
return localMatched;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void remoteConfirmationSucceeded() {
|
||||
lock.lock();
|
||||
try {
|
||||
remoteCompared = true;
|
||||
remoteMatched = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.remoteConfirmationSucceeded();
|
||||
}
|
||||
|
||||
void remoteConfirmationFailed() {
|
||||
lock.lock();
|
||||
try {
|
||||
remoteCompared = true;
|
||||
remoteMatched = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners) l.remoteConfirmationFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactExchangeSucceeded(Author remoteAuthor) {
|
||||
String name = remoteAuthor.getName();
|
||||
lock.lock();
|
||||
try {
|
||||
remoteName = name;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
for (InvitationListener l : listeners)
|
||||
l.pseudonymExchangeSucceeded(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void duplicateContact(Author remoteAuthor) {
|
||||
// TODO differentiate
|
||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contactExchangeFailed() {
|
||||
for (InvitationListener l : listeners) l.pseudonymExchangeFailed();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class InvitationModule {
|
||||
|
||||
@Provides
|
||||
InvitationTaskFactory provideInvitationTaskFactory(
|
||||
InvitationTaskFactoryImpl invitationTaskFactory) {
|
||||
return invitationTaskFactory;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.briarproject.bramble.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTask;
|
||||
import org.briarproject.bramble.api.invitation.InvitationTaskFactory;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class InvitationTaskFactoryImpl implements InvitationTaskFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
private final ContactExchangeTask contactExchangeTask;
|
||||
private final IdentityManager identityManager;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
InvitationTaskFactoryImpl(CryptoComponent crypto,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory,
|
||||
ContactExchangeTask contactExchangeTask,
|
||||
IdentityManager identityManager, PluginManager pluginManager) {
|
||||
this.crypto = crypto;
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
this.contactExchangeTask = contactExchangeTask;
|
||||
this.identityManager = identityManager;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvitationTask createTask(int localCode, int remoteCode) {
|
||||
return new ConnectorGroup(crypto, bdfReaderFactory, bdfWriterFactory,
|
||||
contactExchangeTask, identityManager, pluginManager,
|
||||
localCode, remoteCode);
|
||||
}
|
||||
}
|
||||
@@ -50,10 +50,9 @@ class KeyAgreementConnector {
|
||||
private final PluginManager pluginManager;
|
||||
private final CompletionService<KeyAgreementConnection> connect;
|
||||
|
||||
private final List<KeyAgreementListener> listeners =
|
||||
new ArrayList<KeyAgreementListener>();
|
||||
private final List<KeyAgreementListener> listeners = new ArrayList<>();
|
||||
private final List<Future<KeyAgreementConnection>> pending =
|
||||
new ArrayList<Future<KeyAgreementConnection>>();
|
||||
new ArrayList<>();
|
||||
|
||||
private volatile boolean connecting = false;
|
||||
private volatile boolean alice = false;
|
||||
@@ -65,8 +64,7 @@ class KeyAgreementConnector {
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.pluginManager = pluginManager;
|
||||
connect = new ExecutorCompletionService<KeyAgreementConnection>(
|
||||
ioExecutor);
|
||||
connect = new ExecutorCompletionService<>(ioExecutor);
|
||||
}
|
||||
|
||||
public Payload listen(KeyPair localKeyPair) {
|
||||
@@ -75,8 +73,7 @@ class KeyAgreementConnector {
|
||||
byte[] commitment = crypto.deriveKeyCommitment(
|
||||
localKeyPair.getPublic().getEncoded());
|
||||
// Start all listeners and collect their descriptors
|
||||
List<TransportDescriptor> descriptors =
|
||||
new ArrayList<TransportDescriptor>();
|
||||
List<TransportDescriptor> descriptors = new ArrayList<>();
|
||||
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
|
||||
KeyAgreementListener l =
|
||||
plugin.createKeyAgreementListener(commitment);
|
||||
@@ -132,10 +129,7 @@ class KeyAgreementConnector {
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
} catch (ExecutionException | IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user