Compare commits

...

178 Commits

Author SHA1 Message Date
akwizgran
a872851a78 Revert minSdkVersion to 21 to fix Robolectric tests. 2023-09-05 17:29:05 +01:00
akwizgran
f3050f9fb8 Fix packaging of Tor binaries with minSdkVersion >= 23. 2023-09-05 17:07:59 +01:00
akwizgran
a3ba1ac91e Fix Gradle Witness. 2023-09-05 16:38:36 +01:00
akwizgran
0ac4b5c613 Don't delete .gitkeep when cleaning. 2023-09-05 15:27:01 +01:00
akwizgran
f4425acfaf Keep jniLibs dir, as it's now an input of the clean task. 2023-09-05 15:23:33 +01:00
akwizgran
7b65c63bc9 Package Android binaries, enable desugaring, bump min API level.
FIXME: Desugaring ConcurrentHashMap.newKeySet() requires
desugar_jdk_libs version 2, which requires bumping the Android Gradle
plugin version. This seems to have broken Gradle Witness.
2023-09-05 14:50:23 +01:00
akwizgran
bf2de56abe Use SqliteDatabase as default implementation. 2023-09-05 10:48:58 +01:00
akwizgran
8d7ac49bff Enable secure_delete for SQLite. 2023-09-05 10:48:36 +01:00
akwizgran
9b2c8b0f98 Fix default value for sync versions. 2023-09-05 10:37:07 +01:00
akwizgran
b7c0bc468f WIP: Create indexes on foreign key columns if needed. 2023-09-04 17:42:02 +01:00
akwizgran
852f3fd78b WIP: Temporarily skip failing test so we can run performance test. 2023-09-01 11:51:05 +01:00
akwizgran
6734284585 WIP: Add SQLite DB backend using sqlite-jdbc-crypt. 2023-09-01 11:42:24 +01:00
akwizgran
87ef5e58ee Update Play Store metadata. 2023-08-28 16:54:13 +01:00
akwizgran
b8b5e6c201 Update Play Store metadata. 2023-08-24 17:44:35 +01:00
akwizgran
b68d24dca5 Bump version numbers for 1.5.6 release. 2023-08-23 10:45:39 +01:00
Torsten Grote
8bb3ea8a85 Merge branch 'no-tv-for-you' into 'master'
Remove support for Android TV

See merge request briar/briar!1808
2023-08-23 07:51:20 +00:00
Torsten Grote
e13563952b Merge branch 'update-play-store-description' into 'master'
Add links to Play Store description

See merge request briar/briar!1807
2023-08-23 07:49:08 +00:00
Torsten Grote
c74ebabcd1 Merge branch 'update-readme' into 'master'
Update readme: add privacy policy, remove Flattr

See merge request briar/briar!1806
2023-08-23 07:47:35 +00:00
akwizgran
47b8f47f07 Remove support for Android TV.
Google requires apps that support Android TV to be published as app bundles.
2023-08-22 15:59:20 +01:00
akwizgran
d0feacd38f Add links to Play Store description. 2023-08-22 15:35:04 +01:00
akwizgran
2844adb8fa Bump version numbers for 1.5.5 release. 2023-08-21 14:54:06 +01:00
akwizgran
f02dcc9f70 Update translations. 2023-08-21 14:53:21 +01:00
akwizgran
8ab7eb7edf Update readme: add privacy policy, remove Flattr. 2023-08-21 14:44:29 +01:00
akwizgran
1ef1ccc1f7 Merge branch 'fix-group-invitation-state' into 'master'
fix SharingState for private group creator

See merge request briar/briar!1805
2023-08-15 15:33:37 +00:00
akwizgran
c7e382c1af Update translations. 2023-08-15 13:33:02 +01:00
ialokim
38a7217c3f fix SharingState for private group creator 2023-08-14 18:05:48 +02:00
Torsten Grote
6d3e81a074 Merge branch 'tor-0.4.7.14' into 'master'
Upgrade Tor to 0.4.7.14

See merge request briar/briar!1804
2023-08-11 12:44:57 +00:00
akwizgran
4591de2017 Upgrade Tor to 0.4.7.14. 2023-08-08 16:54:09 +01:00
Torsten Grote
6da34fac84 Merge branch 'bdf-javadocs' into 'master'
Add BDF javadocs

See merge request briar/briar!1801
2023-08-07 14:16:22 +00:00
Torsten Grote
810ac24cee Merge branch 'onionwrapper-0.0.5' into 'master'
Upgrade onionwrapper to 0.0.5

See merge request briar/briar!1803
2023-08-07 13:48:36 +00:00
akwizgran
704f69c9fd Upgrade onionwrapper to 0.0.5. 2023-08-07 14:17:11 +01:00
akwizgran
952ee42ad1 Merge branch 'blog-txns' into 'master'
Add transactional versions of BlogManager methods and a bug fix

See merge request briar/briar!1802
2023-07-13 20:30:21 +00:00
Torsten Grote
f61b09d5a9 Fix BlogManager tests after last commits 2023-07-13 14:50:05 -03:00
Torsten Grote
8f735d176e Add transactional versions of BlogManager methods 2023-07-13 13:01:30 -03:00
Torsten Grote
c47253fc5f Mark our own reblogs as read automatically 2023-07-13 12:54:17 -03:00
akwizgran
b1cc63cd49 Deprecate methods for handling non-canonical BDF. 2023-07-05 15:23:14 +01:00
akwizgran
8cd6546840 Add javadocs for BDF classes. 2023-07-05 15:23:08 +01:00
akwizgran
7a0fb74c09 Merge branch '2266-target-sdk-33' into 'master'
Target SDK 33

Closes #2266

See merge request briar/briar!1800
2023-07-03 10:55:46 +00:00
akwizgran
882f536b8d Don't try to get Bluetooth address from settings. 2023-06-30 18:14:12 +01:00
Torsten Grote
74f8e84a9b React to device light idle mode in DozeWatchdog as well 2023-06-29 10:58:32 -03:00
Torsten Grote
23df2a41c2 Add @NotNullByDefault annotation to ConditionManagers 2023-06-29 10:58:32 -03:00
Torsten Grote
c77eaf16d9 Log more mode changes in AndroidBatteryManager 2023-06-29 10:58:31 -03:00
Torsten Grote
9a6bb4b203 Set dozed to true when we are in LowPowerStandby 2023-06-29 10:58:31 -03:00
Torsten Grote
3d237a9104 Introduce tryToStartActivity() helper method 2023-06-29 10:58:31 -03:00
Torsten Grote
fa216ffc6f Move requestEnableWiFi() into AbstractConditionManager 2023-06-29 10:58:31 -03:00
Torsten Grote
a34631d36c Catch ActivityNotFoundException in places where we launch external intents 2023-06-29 10:58:31 -03:00
Torsten Grote
45cda191e5 Log changes to DeviceLightIdleMode in AndroidBatteryManager 2023-06-29 10:58:31 -03:00
Torsten Grote
2495b6f5c0 Add LowPowerStandby stub to DozeWatchdogImpl 2023-06-29 10:58:31 -03:00
Torsten Grote
03fc504f7d Log changes to LowPowerStandby in AndroidBatteryManager 2023-06-29 10:58:30 -03:00
Torsten Grote
d19062e319 Don't disable hotspot start button after click
to avoid issues when coming back to the screen after granting permissions.
2023-06-29 10:58:30 -03:00
Torsten Grote
fdb429ab7a Ask for NEARBY_WIFI_DEVICES permission on SDK 33 and up 2023-06-29 10:58:30 -03:00
Torsten Grote
d0c59a6d75 Target SDK 33 and ask for notification permission
when creating account and when signing in
2023-06-29 10:58:30 -03:00
akwizgran
3bb39c2aa3 Merge branch 'fix-macos-x86-issue' into 'master'
Fix architecture detection for macOS Intel CPUs

See merge request briar/briar!1799
2023-06-28 11:07:26 +00:00
Sebastian Kürten
917fc5e5b6 Fix architecture detection for macOS Intel CPUs 2023-06-28 12:57:20 +02:00
akwizgran
caa078585b Merge branch 'macos3' into 'master'
macOS support

See merge request briar/briar!1790
2023-06-22 17:04:09 +00:00
akwizgran
e68c0c7f4b Merge branch 'onionwrapper-0.0.4' into 'master'
Upgrade onionwrapper to 0.0.4

See merge request briar/briar!1798
2023-06-22 12:26:21 +00:00
akwizgran
a6b3749fb6 Extend comment explaining TorState -> State mapping. 2023-06-22 13:12:58 +01:00
Torsten Grote
a8f6e8e4bd Merge branch 'check-network-status-periodically' into 'master'
Check network status periodically

See merge request briar/briar!1797
2023-06-21 13:32:39 +00:00
akwizgran
4d884601f0 Check more often, only broadcast status if changed. 2023-06-20 17:01:45 +01:00
akwizgran
b71198d9b1 Check network status periodically on JavaSE. 2023-06-20 16:34:45 +01:00
Sebastian Kürten
079c6e0475 Add comment why we choose a differnt port for headless on macOS 2023-06-20 12:21:11 +02:00
Sebastian Kürten
3a0f8ed85c Document building of macOS headless jars and fix included native binaries on macOS 2023-06-15 18:07:18 +02:00
Sebastian Kürten
57f7501780 macOS support 2023-06-15 12:48:01 +02:00
akwizgran
3cc5699fe0 Upgrade onionwrapper to 0.0.4. 2023-06-14 17:06:10 +01:00
akwizgran
7d761710e6 Bump version numbers for 1.5.4 release. 2023-06-02 13:53:20 +01:00
Torsten Grote
7461d3c943 Merge branch '2434-use-us-locale-for-lowercasing-onion-hostname' into 'master'
Use US locale for lowercasing onion hostname

Closes #2434

See merge request briar/briar!1796
2023-05-31 16:27:24 +00:00
akwizgran
9291613175 Fix some other uses of toLowerCase() without a locale. 2023-05-30 22:06:18 +01:00
akwizgran
ce6739a9fd Use US locale for lowercasing onion hostname. 2023-05-30 22:00:41 +01:00
akwizgran
1f1a97f62d Bump version numbers for 1.5.3 release. 2023-05-24 11:33:49 +01:00
akwizgran
7a33d26533 Merge branch 'new-handshake' 2023-05-24 11:22:23 +01:00
akwizgran
f2c85f37be Merge branch '2391-share-link' into 'master'
Share a link to the Briar download page via another app

Closes #2391

See merge request briar/briar!1795
2023-05-22 15:35:43 +00:00
Torsten Grote
8e3fa872fd Move About settings item to the bottom 2023-05-17 08:53:24 -03:00
akwizgran
0d1e81ebdb Merge branch 'use-default-secure-random-provider-on-macos' into 'master'
Use system default secure random provider on macOS

Closes briar-desktop#132

See merge request briar/briar!1794
2023-05-17 09:25:42 +00:00
Sebastian Kürten
bded4e7bc8 Use system default secure random provider on macOS 2023-05-17 11:13:43 +02:00
Torsten Grote
bf1a5cf218 Allow sharing download link for Briar from settings actions 2023-05-16 16:55:19 -03:00
akwizgran
dd7a638984 Merge branch 'fa-string-fix' into 'master'
Fix translation

See merge request briar/briar!1793
2023-05-16 10:38:47 +00:00
paul
942222131e Fix translation. 2023-05-15 21:48:02 +00:00
akwizgran
643757e407 Bump version numbers for 1.5.2 release. 2023-05-15 15:48:50 +01:00
akwizgran
7c530ad7a3 Update translations. 2023-05-15 15:48:00 +01:00
Torsten Grote
23b2dfa4a8 Merge branch 'conversationmanager-txn' into 'master'
Add transactional versions to delete message functions

See merge request briar/briar!1792
2023-05-15 12:03:53 +00:00
ialokim
ce10e6770f add transactional versions to delete message functions 2023-05-12 23:58:59 +02:00
Torsten Grote
b88dbee881 Merge branch 'onionwrapper-0.0.2' into 'master'
Upgrade onionwrapper to 0.0.2 and dont-kill-me-lib to 0.2.7

See merge request briar/briar!1791
2023-05-10 17:00:38 +00:00
akwizgran
0ca21ad4c0 Upgrade onionwrapper to 0.0.2 and dont-kill-me-lib to 0.2.7. 2023-05-09 15:23:17 +01:00
akwizgran
a14f62dcc3 Update translations. 2023-05-05 14:57:46 +01:00
akwizgran
f0c1ebcc1b Merge branch 'kill-android4' into 'master'
Drop support for Android 4

See merge request briar/briar!1770
2023-05-05 13:54:07 +00:00
akwizgran
4a4b04bec3 Rename version constant. 2023-04-26 17:10:23 +01:00
Torsten Grote
6f57ec8281 Merge branch 'privategroup-testdata' into 'master'
TestDataCreator with support for private groups

See merge request briar/briar!1788
2023-04-26 12:38:53 +00:00
Sebastian Kürten
0eb0bbdc99 Add ability to add private group test data in settings 2023-04-26 00:05:49 +02:00
Torsten Grote
76344344d2 Downgrade material library due to upstream bug
https://github.com/material-components/material-components-android/issues/3191
2023-04-25 11:29:51 -03:00
akwizgran
624f11a61f Bump version numbers for 1.5.1 release. 2023-04-21 15:51:55 +01:00
Torsten Grote
fbc32830bd Force kotlin standard lib to latest version to prevent jetifier issues 2023-04-19 11:02:33 -03:00
Torsten Grote
145117a1dc Update most of the things 2023-04-19 11:02:33 -03:00
Torsten Grote
6ed55bcd7d Drop support for Android 4
new minSdk is 21
2023-04-19 11:02:31 -03:00
akwizgran
c6a284bd6d Bump version numbers for 1.5.0 release. 2023-04-19 14:23:20 +01:00
akwizgran
9d4d992009 Update translations. 2023-04-19 14:21:38 +01:00
Torsten Grote
e92eb1c699 Merge branch 'enable-mailbox-in-release-builds' into 'master'
Enable mailbox support in release builds

See merge request briar/briar!1789
2023-04-19 13:17:53 +00:00
akwizgran
07e56f7086 Remove mailbox feature flag. 2023-04-18 14:18:58 +01:00
akwizgran
fe31e60e66 Merge branch '2420-obsolete-bluetooth-permission-api-32' into 'master'
Require obsolete Bluetooth permission on API 32 (and counting)

Closes #2420

See merge request briar/briar!1782
2023-04-18 11:43:59 +00:00
akwizgran
7810e7e848 Enable mailbox support in release builds. 2023-04-18 12:37:33 +01:00
ialokim
f8015272f4 private group support for TestDataCreator 2023-04-06 16:43:18 +02:00
Torsten Grote
2566105f13 Merge branch 'tor-wrapper-library' into 'master'
Use Tor wrapper library

See merge request briar/briar!1787
2023-04-03 19:27:58 +00:00
akwizgran
cab8f834bd Convert onionwrapper from a submodule to a dependency. 2023-03-29 17:01:30 +01:00
akwizgran
ec0a754289 Remove BridgeTest from CI config. 2023-03-29 11:58:38 +01:00
akwizgran
e81fe44ea1 Update onionwrapper. 2023-03-28 18:09:43 +01:00
akwizgran
e399b9196a Merge branch 'tor-plugin-refactoring' into 'master'
Refactor Tor plugin to separate out reusable code

See merge request briar/briar!1786
2023-03-28 16:42:17 +00:00
akwizgran
aadbd3a662 Fix dependencies for headless jar tasks. 2023-03-28 17:41:22 +01:00
akwizgran
f4fd65aee4 Remove jtorctl dependency. 2023-03-28 17:28:15 +01:00
akwizgran
61e7d2ebf9 Move Tor wrapper to library. 2023-03-28 17:18:05 +01:00
akwizgran
06dd8c65aa Fix parsing of bootstrap percentage. 2023-03-28 15:44:25 +01:00
akwizgran
2f351b318e Move CircumventionProvider classes to wrapper package. 2023-03-28 15:40:48 +01:00
akwizgran
a468af94db Add bootstrap percentage and HS desc uploads to observer interface. 2023-03-28 11:31:20 +01:00
akwizgran
49f10e7e82 Move wake lock code to dont-kill-me-lib. 2023-03-28 10:58:45 +01:00
akwizgran
01b1741e83 Factor out Tor wrapper from plugin 2023-03-27 12:02:05 +01:00
akwizgran
b7003a3587 Update translations. 2023-03-20 10:59:32 +00:00
akwizgran
3dbf327937 Merge branch 'backport-os-check-algorithm' into 'master'
Backport OS-check logic from Compose Multiplatform

See merge request briar/briar!1785
2023-03-10 16:28:33 +00:00
akwizgran
462f57c966 Upgrade handshake protocol to new key agreement method. 2023-03-10 16:05:59 +00:00
akwizgran
8d20c5d8b8 Reify RecordPredicate for easier testing. 2023-03-10 15:15:29 +00:00
Sebastian Kürten
73d806f8b9 Backport OS-check logic from Compose Multiplatform 2023-03-09 17:03:08 +01:00
akwizgran
f1ae57b213 Merge branch 'mailbox-fix' into 'master'
Fix mailbox integration tests

See merge request briar/briar!1784
2023-03-09 15:42:22 +00:00
Torsten Grote
cae9efb4bf Fix integration tests by using dynamic webserver port of mailbox 2023-03-09 12:29:48 -03:00
Torsten Grote
39ac737015 Merge branch 'no-personalized-learning' into 'master'
Set "no personalized learning" flag for all text input

See merge request briar/briar!1783
2023-03-09 14:14:11 +00:00
akwizgran
edd3310d03 Set "no personalized learning" flag for all text input. 2023-03-09 10:52:46 +00:00
akwizgran
a09d88daa8 Add Slovak to list of available languages. 2023-03-09 09:53:43 +00:00
akwizgran
3dc984659d Update translations. 2023-03-09 09:41:02 +00:00
akwizgran
f580525734 Require obsolete Bluetooth permission on API 32 (and counting). 2023-03-06 17:46:34 +00:00
akwizgran
fbf0f63ff7 Merge branch 'introduction-manager-txn' into 'master'
Transactional versions for introduction manager and private group invitation manager

See merge request briar/briar!1781
2023-03-06 12:08:34 +00:00
ialokim
ee9234e12e transactional versions for GroupInvitationManager 2023-03-05 22:10:50 +01:00
akwizgran
2657e2bc08 Merge branch '2245-toast-name-wrong' into 'master'
Remove wrong name from "Contact already exists"

Closes #2245

See merge request briar/briar!1780
2023-02-27 14:26:27 +00:00
Torsten Grote
3c40c11dfb remove wrong name from "Contact already exists" 2023-02-27 10:43:12 -03:00
akwizgran
3bdbabf38a Merge branch 'no-longer-use-deprecated-double-valueof' into 'master'
No longer use deprecated Double.valueOf()

See merge request briar/briar!1779
2023-02-27 12:45:01 +00:00
Sebastian Kürten
a378c24af8 No longer use deprecated Double.valueOf() 2023-02-27 12:22:27 +01:00
ialokim
b09ea495e7 add transactional versions to introductionManager and privateGroupManager 2023-02-24 18:43:08 +01:00
akwizgran
070165f608 Bump version numbers for 1.4.23 release. 2023-02-24 14:04:57 +00:00
Torsten Grote
445f174275 Merge branch 'update-tor-bridges' into 'master'
Update Tor bridges

See merge request briar/briar!1778
2023-02-24 13:28:27 +00:00
akwizgran
ea5af72878 Add some non-default bridges. 2023-02-24 12:31:22 +00:00
akwizgran
ecf2e75424 Remove some bridges not known to Onionoo. 2023-02-24 12:30:24 +00:00
akwizgran
feebd89029 Remove some failing bridges. 2023-02-24 12:23:37 +00:00
akwizgran
cf723f8002 Merge branch 'dont-unpack-tor' into 'master'
Upgrade tor, obfs4proxy, snoflake and convert tor to regular dependencies in bramble-java

See merge request briar/briar!1775
2023-02-24 12:13:31 +00:00
akwizgran
b8e743021c Update translations. 2023-02-24 12:11:58 +00:00
Sebastian Kürten
b785b6c10f Upgrade tor 2023-02-24 13:05:46 +01:00
Sebastian Kürten
26ec200f50 Convert tor to regular dependencies 2023-02-24 12:51:50 +01:00
akwizgran
82efb0d044 Upgrade Tor, obfs4 and snowflake; use new artifact layout. 2023-02-23 16:25:08 +01:00
akwizgran
4ac4ba13d4 Merge branch 'split-app' into 'master'
Split out APP for check_reproducibility CI job

See merge request briar/briar!1777
2023-02-22 17:06:31 +00:00
Torsten Grote
0844cd3547 Split out APP for check_reproducibility CI job 2023-02-21 16:14:02 -03:00
Torsten Grote
69e6648ded Merge branch '2415-check-bt-socket-streams-not-null' into 'master'
Check that BluetoothSocket's input and output streams aren't null

Closes #2415

See merge request briar/briar!1776
2023-02-21 13:38:49 +00:00
akwizgran
518aeb38b9 Check that BluetoothSocket's input and output streams aren't null. 2023-02-21 13:29:35 +00:00
akwizgran
7e5e61fc05 Merge branch 'dont-package-all-snowflake-architectures-into-headless-jars' into 'master'
Don't package all snowflake architectures into headless jars

See merge request briar/briar!1774
2023-02-21 11:34:04 +00:00
Sebastian Kürten
6ecb44bcaa Don't package all snowflake architectures into headless jars 2023-02-21 09:38:24 +01:00
akwizgran
f02bbebf6c Bump version numbers for 1.4.22 release. 2023-02-20 17:29:41 +00:00
akwizgran
b8612715f8 Merge branch 'check_repro' into 'master'
Fix variable substitution for check_reproducibility

See merge request briar/briar!1773
2023-02-20 17:26:25 +00:00
Torsten Grote
b86ddfa22f Fix variable substitution for check_reproducibility 2023-02-20 14:08:19 -03:00
akwizgran
0dd4d86f4a Bump version numbers for 1.4.21 release. 2023-02-20 16:00:45 +00:00
akwizgran
17e0829f42 Update translations. 2023-02-20 16:00:05 +00:00
Torsten Grote
938d8b71a0 Merge branch 'bdf-cleanup' into 'master'
Clean up some BDF quirks

See merge request briar/briar!1772
2023-02-20 14:15:47 +00:00
akwizgran
1b808584b6 Replace some more longs with ints. 2023-02-20 13:53:40 +00:00
akwizgran
36db5b48ef Remove methods for manually reading lists and dictionaries. 2023-02-20 13:05:38 +00:00
akwizgran
ccd6ed9ff0 Add fast path for writing BdfDictionaries. 2023-02-20 11:56:13 +00:00
akwizgran
0ced10b3a9 Use getInt() in a couple more places. 2023-02-20 11:33:48 +00:00
akwizgran
98064e9efe Remove BdfWriter methods for manually constructing lists and dicts. 2023-02-18 17:36:02 +00:00
akwizgran
63172ef2e4 Add 32-bit int methods to BdfList and BdfDictionary.
We use these a lot so it's useful to have built-in support.

Also refactor BdfList and BdfDictionary so the getters that take default values behave like the other getters. This simplifies the semantics and allows duplicated code to be removed.

Add comprehensive tests for BdfList and BdfDictionary.
2023-02-18 17:36:02 +00:00
akwizgran
7a854e70cb Add BdfReader methods for 32-bit ints.
We use these a lot so it's convenient to have built-in support.

Also make BdfReaderImpl and BdfWriterImpl final to enable compiler optimisations.
2023-02-18 17:36:02 +00:00
akwizgran
ac8a4db457 Add support for reading and writing BDF in canonical form.
Existing transport property updates may not be in canonical form, so we need to parse them leniently.
2023-02-18 17:36:02 +00:00
akwizgran
5a09530670 Reject invalid UTF-8 instead of ignoring it. 2023-02-18 17:10:57 +00:00
akwizgran
4fe91bacc6 Merge branch 'sync-record-reader-tests' into 'master'
Add some tests for sync record reader

See merge request briar/briar!1771
2023-02-17 17:38:04 +00:00
akwizgran
7f70a1519b Make message fields local. 2023-02-17 17:28:10 +00:00
akwizgran
c92ee0458e Add some tests for sync record reader. 2023-02-17 17:19:59 +00:00
akwizgran
10b1fe756d Merge branch 'reproduce-headless' into 'master'
Check reproducibility of headless releases

See merge request briar/briar!1769
2023-02-13 16:21:50 +00:00
Torsten Grote
1a2a250be0 Check reproducibility of headless releases 2023-02-10 16:43:01 -03:00
akwizgran
a621b8077e Merge branch 'update-reproducer-release-tag' into 'master'
Update briar-reproducer release tag

See merge request briar/briar!1768
2023-02-10 14:17:15 +00:00
akwizgran
19084d4060 Merge branch 'mailbox-version-mismatch' into 'master'
Show mailbox version issues before connection failures

See merge request briar/briar!1767
2023-02-10 13:57:36 +00:00
akwizgran
2f73ee1b57 Update briar-reproducer release tag. 2023-02-10 13:54:44 +00:00
Torsten Grote
45fa12c0b3 Show mailbox version issues before connection failures 2023-02-09 13:10:02 -03:00
akwizgran
4253bbaaf5 Update translations. 2023-02-06 12:40:04 +00:00
akwizgran
8c2e58796b Merge branch 'mailbox-convert-qr' into 'master'
Add convenience method for converting mailbox pairing text into QR code payload

See merge request briar/briar!1766
2023-02-03 14:34:04 +00:00
Torsten Grote
3f13e7e9c3 Add convenience method for converting mailbox pairing text into QR code payload 2023-02-03 11:18:25 -03:00
akwizgran
421a93b9a6 Merge branch 'rss-from-file' into 'master'
Allow to import RSS feeds from a file

See merge request briar/briar!1765
2023-01-31 13:17:21 +00:00
Torsten Grote
8a088638db Don't show success fragment for RSS file import 2023-01-31 09:57:51 -03:00
Torsten Grote
a888c5f632 Allow to import RSS feeds from a file 2023-01-30 15:31:25 -03:00
324 changed files with 8048 additions and 5906 deletions

View File

@@ -83,29 +83,14 @@ android test:
test_reproducible: test_reproducible:
stage: check_reproducibility stage: check_reproducibility
script: script:
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline" - "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[APP]='briar' -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
- "curl -X POST -F token=${RELEASE_JAR_CHECK_TOKEN} -F ref=main -F variables[APP]='briar-headless' -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/307/trigger/pipeline"
only: only:
- tags - tags
.optional_tests: mailbox integration test:
stage: optional_tests stage: optional_tests
extends: .base-test extends: .base-test
bridge test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
allow_failure: false
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 4h
mailbox integration test:
extends: .optional_tests
rules: rules:
- changes: - changes:
- mailbox-integration-tests/**/* - mailbox-integration-tests/**/*
@@ -119,3 +104,12 @@ mailbox integration test:
script: script:
- (cd briar-mailbox; git fetch; git reset --hard origin/main) - (cd briar-mailbox; git fetch; git reset --hard origin/main)
- MAILBOX_INTEGRATION_TESTS=true ./gradlew --info mailbox-integration-tests:test - MAILBOX_INTEGRATION_TESTS=true ./gradlew --info mailbox-integration-tests:test
db_performance_comparison_test:
extends: .base-test
stage: optional_tests
script:
- OPTIONAL_TESTS=org.briarproject.bramble.db.H2SqliteDatabasePerformanceComparisonTest ./gradlew --info -Djava.security.egd=file:/dev/urandom :bramble-core:test --tests H2SqliteDatabasePerformanceComparisonTest
rules:
- when: manual

View File

@@ -1,28 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--tests &quot;org.briarproject.bramble.plugin.tor.BridgeTest&quot;" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":bramble-java:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,7 +1,7 @@
# Briar # Briar
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate.
Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. Unlike traditional messaging apps, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices.
If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance. If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
@@ -14,14 +14,16 @@ You can also [download the APK file](https://briarproject.org/apk) directly from
our site. our site.
## Useful links ## Useful links
[briarproject.org](https://briarproject.org/) [Project website](https://briarproject.org/)
[Source code](https://code.briarproject.org/briar/briar/tree/master) [Source code](https://code.briarproject.org/briar/briar/tree/master)
[Manual](https://briarproject.org/manual/) [User manual](https://briarproject.org/manual/)
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home) [Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
[Privacy policy](https://briarproject.org/privacy)
## Reproducible builds ## Reproducible builds
We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer) We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer)
@@ -33,5 +35,5 @@ for reproduction.
## Donate ## Donate
[![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate) [![Flattr this](https://briarproject.org/img/flattr-badge-large.png "Flattr this")](https://flattr.com/t/592836/) [![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate)
Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K

View File

@@ -4,3 +4,4 @@ build
.settings .settings
src/main/res/raw/*.zip src/main/res/raw/*.zip
src/main/jniLibs src/main/jniLibs
!src/main/jniLibs/.gitkeep

View File

@@ -11,10 +11,12 @@ android {
} }
defaultConfig { defaultConfig {
minSdkVersion 16 // FIXME: sqlite-jdbc-crypt uses __register_atfork which is only available on API >= 23.
targetSdkVersion 31 // We might be able to solve this by recompiling (or asking upstream to recompile)
versionCode 10420 minSdkVersion 21
versionName "1.4.20" targetSdkVersion 33
versionCode 10506
versionName "1.5.6"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -37,9 +39,12 @@ android {
configurations { configurations {
tor tor
sqliteJdbcCrypt
} }
dependencies { dependencies {
api 'org.briarproject:dont-kill-me-lib:0.2.7'
// In theory this dependency shouldn't be needed, but without it Android Studio's linter will // In theory this dependency shouldn't be needed, but without it Android Studio's linter will
// complain about unresolved symbols for bramble-api test classes in bramble-android tests, // complain about unresolved symbols for bramble-api test classes in bramble-android tests,
// even though the bramble-api test classes are provided by the testImplementation dependency // even though the bramble-api test classes are provided by the testImplementation dependency
@@ -49,11 +54,14 @@ dependencies {
implementation project(':bramble-core') implementation project(':bramble-core')
implementation 'androidx.annotation:annotation:1.5.0' implementation 'androidx.annotation:annotation:1.5.0'
implementation "org.briarproject:onionwrapper-android:$onionwrapper_version"
tor "org.briarproject:tor-android:$tor_version" tor "org.briarproject:tor-android:$tor_version"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version" tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
tor "org.briarproject:snowflake-android:$snowflake_version" tor "org.briarproject:snowflake-android:$snowflake_version"
sqliteJdbcCrypt "io.github.willena:sqlite-jdbc:$sqlite_jdbc_crypt_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
@@ -66,51 +74,50 @@ dependencies {
testImplementation "org.jmock:jmock-imposters:$jmock_version" testImplementation "org.jmock:jmock-imposters:$jmock_version"
} }
def torLibsDir = 'src/main/jniLibs' def jniLibsDir = 'src/main/jniLibs'
task cleanTorBinaries { task cleanJniLibs {
outputs.dir torLibsDir inputs.dir jniLibsDir
outputs.dir jniLibsDir
doLast { doLast {
delete fileTree(torLibsDir) { include '**/*.so' } delete fileTree(jniLibsDir).filter { it.name.endsWith('.so') }
} }
} }
clean.dependsOn cleanTorBinaries clean.dependsOn cleanJniLibs
task unpackTorBinaries { task unpackJniLibs {
outputs.dir torLibsDir outputs.dir jniLibsDir
doLast { doLast {
configurations.tor.each { outer -> // Tor
zipTree(outer).each { inner ->
if (inner.name.endsWith('_arm_pie.zip')) {
copy { copy {
from zipTree(inner) from configurations.tor.collect { zipTree(it) }
into torLibsDir into jniLibsDir
rename '(.*)', 'armeabi-v7a/lib$1.so'
} }
} else if (inner.name.endsWith('_arm64_pie.zip')) { // sqlite-jdbc-crypt
def archMap = [
aarch64: 'arm64-v8a',
arm : 'armeabi-v7a',
x86 : 'x86',
x86_64 : 'x86_64'
]
configurations.sqliteJdbcCrypt.collect { File artifact ->
zipTree(artifact).each { File f ->
for (String arch : archMap.keySet()) {
if (f.absolutePath.endsWith("/Linux-Android/$arch/libsqlitejdbc.so")) {
def archDir = new File(jniLibsDir, archMap.get(arch))
archDir.mkdirs()
copy { copy {
from zipTree(inner) from f
into torLibsDir into archDir
rename '(.*)', 'arm64-v8a/lib$1.so'
} }
} else if (inner.name.endsWith('_x86_pie.zip')) { break
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86/lib$1.so'
}
} else if (inner.name.endsWith('_x86_64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86_64/lib$1.so'
} }
} }
} }
} }
} }
dependsOn cleanTorBinaries dependsOn cleanJniLibs
} }
preBuild.dependsOn unpackTorBinaries preBuild.dependsOn unpackJniLibs

View File

@@ -6,6 +6,9 @@
-dontwarn org.h2.** -dontwarn org.h2.**
-dontnote org.h2.** -dontnote org.h2.**
# Keep sqlite-jdbc-crypt classes that are loaded via reflection or accessed via JNI
-keep class org.sqlite.** { *; }
-keep class dagger.** { *; } -keep class dagger.** { *; }
-dontwarn dagger.** -dontwarn dagger.**
-dontnote dagger.** -dontnote dagger.**

View File

@@ -8,10 +8,10 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- The BLUETOOTH permission was supposed to be removed in API 31 but is still needed on some Xiaomi/Redmi/POCO devices running API 31 --> <!-- The BLUETOOTH permission was supposed to be removed in API 31 but is still needed on some Xiaomi/Redmi/POCO devices running API 31 and Nubia devices running API 32 -->
<uses-permission <uses-permission
android:name="android.permission.BLUETOOTH" android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="31" /> android:maxSdkVersion="32" />
<uses-permission <uses-permission
android:name="android.permission.BLUETOOTH_ADMIN" android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" /> android:maxSdkVersion="30" />

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.socks.SocksModule; import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.system.AndroidSystemModule; import org.briarproject.bramble.system.AndroidSystemModule;
import org.briarproject.bramble.system.AndroidTaskSchedulerModule; import org.briarproject.bramble.system.AndroidTaskSchedulerModule;
import org.briarproject.bramble.system.AndroidWakeLockModule;
import org.briarproject.bramble.system.AndroidWakefulIoExecutorModule; import org.briarproject.bramble.system.AndroidWakefulIoExecutorModule;
import org.briarproject.bramble.system.DefaultThreadFactoryModule; import org.briarproject.bramble.system.DefaultThreadFactoryModule;
@@ -19,6 +20,7 @@ import dagger.Module;
AndroidSystemModule.class, AndroidSystemModule.class,
AndroidTaskSchedulerModule.class, AndroidTaskSchedulerModule.class,
AndroidWakefulIoExecutorModule.class, AndroidWakefulIoExecutorModule.class,
AndroidWakeLockModule.class,
DefaultThreadFactoryModule.class, DefaultThreadFactoryModule.class,
CircumventionModule.class, CircumventionModule.class,
DnsModule.class, DnsModule.class,

View File

@@ -20,7 +20,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject; import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir; import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
@@ -105,16 +104,12 @@ class AndroidAccountManager extends AccountManagerImpl
} }
files.add(appContext.getFilesDir()); files.add(appContext.getFilesDir());
addIfNotNull(files, appContext.getExternalCacheDir()); addIfNotNull(files, appContext.getExternalCacheDir());
if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) { for (File file : appContext.getExternalCacheDirs()) {
addIfNotNull(files, file); addIfNotNull(files, file);
} }
}
if (SDK_INT >= 21) {
for (File file : appContext.getExternalMediaDirs()) { for (File file : appContext.getExternalMediaDirs()) {
addIfNotNull(files, file); addIfNotNull(files, file);
} }
}
// Clear the cache directory but don't delete it // Clear the cache directory but don't delete it
File cacheDir = appContext.getCacheDir(); File cacheDir = appContext.getCacheDir();
File[] children = cacheDir.listFiles(); File[] children = cacheDir.listFiles();

View File

@@ -1,19 +0,0 @@
package org.briarproject.bramble.api.system;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface AndroidWakeLock {
/**
* Acquires the wake lock. This has no effect if the wake lock has already
* been acquired.
*/
void acquire();
/**
* Releases the wake lock. This has no effect if the wake lock has already
* been released.
*/
void release();
}

View File

@@ -1,38 +0,0 @@
package org.briarproject.bramble.api.system;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
@NotNullByDefault
public interface AndroidWakeLockManager {
/**
* Creates a wake lock with the given tag. The tag is only used for
* logging; the underlying OS wake lock will use its own tag.
*/
AndroidWakeLock createWakeLock(String tag);
/**
* Runs the given task while holding a wake lock.
*/
void runWakefully(Runnable r, String tag);
/**
* Submits the given task to the given executor while holding a wake lock.
* The lock is released when the task completes, or if an exception is
* thrown while submitting or running the task.
*/
void executeWakefully(Runnable r, Executor executor, String tag);
/**
* Starts a dedicated thread to run the given task asynchronously. A wake
* lock is acquired before starting the thread and released when the task
* completes, or if an exception is thrown while starting the thread or
* running the task.
* <p>
* This method should only be used for lifecycle management tasks that
* can't be run on an executor.
*/
void executeWakefully(Runnable r, String tag);
}

View File

@@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.PowerManager;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.battery.event.BatteryEvent; import org.briarproject.bramble.api.battery.event.BatteryEvent;
@@ -16,10 +17,17 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.RequiresApi;
import static android.content.Intent.ACTION_BATTERY_CHANGED; import static android.content.Intent.ACTION_BATTERY_CHANGED;
import static android.content.Intent.ACTION_POWER_CONNECTED; import static android.content.Intent.ACTION_POWER_CONNECTED;
import static android.content.Intent.ACTION_POWER_DISCONNECTED; import static android.content.Intent.ACTION_POWER_DISCONNECTED;
import static android.os.BatteryManager.EXTRA_PLUGGED; import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED;
import static android.os.PowerManager.ACTION_POWER_SAVE_MODE_CHANGED;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -57,6 +65,12 @@ class AndroidBatteryManager implements BatteryManager, Service {
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_POWER_CONNECTED); filter.addAction(ACTION_POWER_CONNECTED);
filter.addAction(ACTION_POWER_DISCONNECTED); filter.addAction(ACTION_POWER_DISCONNECTED);
filter.addAction(ACTION_POWER_SAVE_MODE_CHANGED);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
if (SDK_INT >= 33) {
filter.addAction(ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
filter.addAction(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED);
}
appContext.registerReceiver(batteryReceiver, filter); appContext.registerReceiver(batteryReceiver, filter);
} }
@@ -76,6 +90,33 @@ class AndroidBatteryManager implements BatteryManager, Service {
eventBus.broadcast(new BatteryEvent(true)); eventBus.broadcast(new BatteryEvent(true));
else if (ACTION_POWER_DISCONNECTED.equals(action)) else if (ACTION_POWER_DISCONNECTED.equals(action))
eventBus.broadcast(new BatteryEvent(false)); eventBus.broadcast(new BatteryEvent(false));
else if (SDK_INT >= 23 &&
ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action) &&
LOG.isLoggable(INFO)) {
LOG.info("Device idle mode changed to: " +
getPowerManager(ctx).isDeviceIdleMode());
} else if (SDK_INT >= 23 &&
ACTION_POWER_SAVE_MODE_CHANGED.equals(action) &&
LOG.isLoggable(INFO)) {
LOG.info("Power save mode changed to: " +
getPowerManager(ctx).isPowerSaveMode());
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED.equals(action)) {
PowerManager powerManager =
ctx.getSystemService(PowerManager.class);
LOG.info("Low power standby now is: " +
powerManager.isLowPowerStandbyEnabled());
} else if (SDK_INT >= 33 && LOG.isLoggable(INFO) &&
ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED.equals(action)) {
PowerManager powerManager = getPowerManager(ctx);
LOG.info("Light idle mode now is: " +
powerManager.isDeviceLightIdleMode());
} }
} }
}
@RequiresApi(api = 23)
private PowerManager getPowerManager(Context ctx) {
return ctx.getSystemService(PowerManager.class);
}
} }

View File

@@ -2,10 +2,10 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.bluetooth; package org.briarproject.bramble.plugin.bluetooth;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
@@ -49,7 +50,6 @@ import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND; import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE; import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.shuffle; import static java.util.Collections.shuffle;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -60,6 +60,7 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@SuppressLint("MissingPermission")
class AndroidBluetoothPlugin extends class AndroidBluetoothPlugin extends
AbstractBluetoothPlugin<BluetoothSocket, BluetoothServerSocket> { AbstractBluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
@@ -253,7 +254,7 @@ class AndroidBluetoothPlugin extends
} else if (ACTION_FOUND.equals(action)) { } else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE); BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
// Ignore Bluetooth LE devices // Ignore Bluetooth LE devices
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) { if (d.getType() != DEVICE_TYPE_LE) {
String address = d.getAddress(); String address = d.getAddress();
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Discovered " + LOG.info("Discovered " +

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -13,7 +14,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.WakefulIoExecutor; import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;

View File

@@ -2,11 +2,11 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLock;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
@@ -33,8 +33,10 @@ class AndroidBluetoothTransportConnection
super(plugin); super(plugin);
this.connectionLimiter = connectionLimiter; this.connectionLimiter = connectionLimiter;
this.socket = socket; this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream( InputStream socketIn = socket.getInputStream();
socket.getInputStream(), plugin.getMaxIdleTime() * 2); if (socketIn == null) throw new IOException();
in = timeoutMonitor.createTimeoutInputStream(socketIn,
plugin.getMaxIdleTime() * 2L);
wakeLock = wakeLockManager.createWakeLock("BluetoothConnection"); wakeLock = wakeLockManager.createWakeLock("BluetoothConnection");
wakeLock.acquire(); wakeLock.acquire();
String address = socket.getRemoteDevice().getAddress(); String address = socket.getRemoteDevice().getAddress();
@@ -48,7 +50,9 @@ class AndroidBluetoothTransportConnection
@Override @Override
protected OutputStream getOutputStream() throws IOException { protected OutputStream getOutputStream() throws IOException {
return socket.getOutputStream(); OutputStream socketOut = socket.getOutputStream();
if (socketOut == null) throw new IOException();
return socketOut;
} }
@Override @Override

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import android.annotation.TargetApi;
import android.app.Application; import android.app.Application;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.LinkAddress; import android.net.LinkAddress;
@@ -37,7 +36,6 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE; import static android.content.Context.WIFI_SERVICE;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.list; import static java.util.Collections.list;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@@ -118,7 +116,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
// If there's no wifi IPv4 address, we might be a client on an // If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+ // IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null) { if (wifi == null) {
return SDK_INT >= 21 ? getWifiClientIpv6Address() : null; return getWifiClientIpv6Address();
} }
// Use the wifi IPv4 address to determine which interface's IPv6 // Use the wifi IPv4 address to determine which interface's IPv6
// address we should return (if the interface has a suitable address) // address we should return (if the interface has a suitable address)
@@ -172,7 +170,6 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
* Returns a link-local IPv6 address for the wifi client interface, or null * Returns a link-local IPv6 address for the wifi client interface, or null
* if there's no such interface or it doesn't have a suitable address. * if there's no such interface or it doesn't have a suitable address.
*/ */
@TargetApi(21)
@Nullable @Nullable
private InetAddress getWifiClientIpv6Address() { private InetAddress getWifiClientIpv6Address() {
// https://issuetracker.google.com/issues/175055271 // https://issuetracker.google.com/issues/175055271
@@ -234,7 +231,6 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
// On API 21 and later, a socket that is not created with the wifi // On API 21 and later, a socket that is not created with the wifi
// network's socket factory may try to connect via another network // network's socket factory may try to connect via another network
private SocketFactory getSocketFactory() { private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault();
// https://issuetracker.google.com/issues/175055271 // https://issuetracker.google.com/issues/175055271
try { try {
for (Network net : connectivityManager.getAllNetworks()) { for (Network net : connectivityManager.getAllNetworks()) {
@@ -302,7 +298,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address(); Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
// If there's no wifi IPv4 address, we might be a client on an // If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+ // IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null && SDK_INT >= 21) { if (wifi == null) {
InetAddress ipv6 = getWifiClientIpv6Address(); InetAddress ipv6 = getWifiClientIpv6Address();
if (ipv6 != null) return new Pair<>(ipv6, false); if (ipv6 != null) return new Pair<>(ipv6, false);
} }

View File

@@ -1,238 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.net.SocketFactory;
import androidx.annotation.ChecksSdkIntAtLeast;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AndroidTorPlugin extends TorPlugin {
private static final List<String> LIBRARY_ARCHITECTURES =
asList("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
private static final String TOR_LIB_NAME = "libtor.so";
private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
private static final String SNOWFLAKE_LIB_NAME = "libsnowflake.so";
private static final Logger LOG =
getLogger(AndroidTorPlugin.class.getName());
private final Application app;
private final AndroidWakeLock wakeLock;
private final File torLib, obfs4Lib, snowflakeLib;
AndroidTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
Application app,
NetworkManager networkManager,
LocationUtils locationUtils,
SocketFactory torSocketFactory,
Clock clock,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture, maxLatency,
maxIdleTime, torDirectory, torSocksPort, torControlPort);
this.app = app;
wakeLock = wakeLockManager.createWakeLock("TorPlugin");
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
torLib = new File(nativeLibDir, TOR_LIB_NAME);
obfs4Lib = new File(nativeLibDir, OBFS4_LIB_NAME);
snowflakeLib = new File(nativeLibDir, SNOWFLAKE_LIB_NAME);
}
@Override
protected int getProcessId() {
return android.os.Process.myPid();
}
@Override
protected long getLastUpdateTime() {
try {
PackageManager pm = app.getPackageManager();
PackageInfo pi = pm.getPackageInfo(app.getPackageName(), 0);
return pi.lastUpdateTime;
} catch (NameNotFoundException e) {
throw new AssertionError(e);
}
}
@Override
protected void enableNetwork(boolean enable) throws IOException {
if (enable) wakeLock.acquire();
super.enableNetwork(enable);
if (!enable) wakeLock.release();
}
@Override
@ChecksSdkIntAtLeast(api = 25)
protected boolean canVerifyLetsEncryptCerts() {
return SDK_INT >= 25;
}
@Override
public void stop() {
super.stop();
wakeLock.release();
}
@Override
protected File getTorExecutableFile() {
return torLib.exists() ? torLib : super.getTorExecutableFile();
}
@Override
protected File getObfs4ExecutableFile() {
return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
}
@Override
protected File getSnowflakeExecutableFile() {
return snowflakeLib.exists()
? snowflakeLib : super.getSnowflakeExecutableFile();
}
@Override
protected void installTorExecutable() throws IOException {
installExecutable(super.getTorExecutableFile(), torLib, TOR_LIB_NAME);
}
@Override
protected void installObfs4Executable() throws IOException {
installExecutable(super.getObfs4ExecutableFile(), obfs4Lib,
OBFS4_LIB_NAME);
}
@Override
protected void installSnowflakeExecutable() throws IOException {
installExecutable(super.getSnowflakeExecutableFile(), snowflakeLib,
SNOWFLAKE_LIB_NAME);
}
private void installExecutable(File extracted, File lib, String libName)
throws IOException {
if (lib.exists()) {
// If an older version left behind a binary, delete it
if (extracted.exists()) {
if (extracted.delete()) LOG.info("Deleted old binary");
else LOG.info("Failed to delete old binary");
}
} else if (SDK_INT < 29) {
// The binary wasn't extracted at install time. Try to extract it
extractLibraryFromApk(libName, extracted);
} else {
// No point extracting the binary, we won't be allowed to execute it
throw new FileNotFoundException(lib.getAbsolutePath());
}
}
private void extractLibraryFromApk(String libName, File dest)
throws IOException {
File sourceDir = new File(app.getApplicationInfo().sourceDir);
if (sourceDir.isFile()) {
// Look for other APK files in the same directory, if we're allowed
File parent = sourceDir.getParentFile();
if (parent != null) sourceDir = parent;
}
List<String> libPaths = getSupportedLibraryPaths(libName);
for (File apk : findApkFiles(sourceDir)) {
ZipInputStream zin = new ZipInputStream(new FileInputStream(apk));
for (ZipEntry e = zin.getNextEntry(); e != null;
e = zin.getNextEntry()) {
if (libPaths.contains(e.getName())) {
if (LOG.isLoggable(INFO)) {
LOG.info("Extracting " + e.getName()
+ " from " + apk.getAbsolutePath());
}
extract(zin, dest); // Zip input stream will be closed
return;
}
}
zin.close();
}
throw new FileNotFoundException(libName);
}
/**
* Returns all files with the extension .apk or .APK under the given root.
*/
private List<File> findApkFiles(File root) {
List<File> files = new ArrayList<>();
findApkFiles(root, files);
return files;
}
private void findApkFiles(File f, List<File> files) {
if (f.isFile() && f.getName().toLowerCase().endsWith(".apk")) {
files.add(f);
} else if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null) {
for (File child : children) findApkFiles(child, files);
}
}
}
/**
* Returns the paths at which libraries with the given name would be found
* inside an APK file, for all architectures supported by the device, in
* order of preference.
*/
private List<String> getSupportedLibraryPaths(String libName) {
List<String> architectures = new ArrayList<>();
for (String abi : AndroidUtils.getSupportedArchitectures()) {
if (LIBRARY_ARCHITECTURES.contains(abi)) {
architectures.add("lib/" + abi + "/" + libName);
}
}
return architectures;
}
}

View File

@@ -2,9 +2,11 @@ package org.briarproject.bramble.plugin.tor;
import android.app.Application; import android.app.Application;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -13,12 +15,13 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorControlPort; import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory; import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort; import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor; import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.AndroidTorWrapper;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.LocationUtils;
import org.briarproject.onionwrapper.TorWrapper;
import java.io.File; import java.io.File;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -28,6 +31,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures; import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
@Immutable @Immutable
@@ -39,13 +43,13 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
@Inject @Inject
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor, AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager, NetworkManager networkManager,
LocationUtils locationUtils, LocationUtils locationUtils,
EventBus eventBus, EventBus eventBus,
SocketFactory torSocketFactory, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
Clock clock, Clock clock,
@@ -55,8 +59,8 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
@TorControlPort int torControlPort, @TorControlPort int torControlPort,
Application app, Application app,
AndroidWakeLockManager wakeLockManager) { AndroidWakeLockManager wakeLockManager) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils, super(ioExecutor, eventExecutor, wakefulIoExecutor, networkManager,
eventBus, torSocketFactory, backoffFactory, resourceProvider, locationUtils, eventBus, torSocketFactory, backoffFactory,
circumventionProvider, batteryManager, clock, crypto, circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort); torDirectory, torSocksPort, torControlPort);
this.app = app; this.app = app;
@@ -79,12 +83,18 @@ public class AndroidTorPluginFactory extends TorPluginFactory {
TorPlugin createPluginInstance(Backoff backoff, TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) { String architecture) {
return new AndroidTorPlugin(ioExecutor, TorWrapper tor = new AndroidTorWrapper(app, wakeLockManager,
wakefulIoExecutor, app, networkManager, locationUtils, ioExecutor, eventExecutor, architecture, torDirectory,
torSocketFactory, clock, resourceProvider, torSocksPort, torControlPort);
circumventionProvider, batteryManager, wakeLockManager, // Android versions 7.1 and newer can verify Let's Encrypt TLS certs
backoff, torRendezvousCrypto, callback, architecture, // signed with the IdentTrust DST Root X3 certificate. Older versions
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort, // of Android consider the certificate to have expired at the end of
torControlPort); // September 2021.
boolean canVerifyLetsEncryptCerts = SDK_INT >= 25;
return new TorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, tor, callback, MAX_LATENCY,
MAX_IDLE_TIME, canVerifyLetsEncryptCerts);
} }
} }

View File

@@ -1,72 +0,0 @@
package org.briarproject.bramble.system;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.Locale;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.content.Context.TELEPHONY_SERVICE;
@NotNullByDefault
class AndroidLocationUtils implements LocationUtils {
private static final Logger LOG =
Logger.getLogger(AndroidLocationUtils.class.getName());
private final Context appContext;
@Inject
AndroidLocationUtils(Application app) {
appContext = app.getApplicationContext();
}
/**
* This guesses the current country from the first of these sources that
* succeeds (also in order of likelihood of being correct):
*
* <ul>
* <li>Phone network. This works even when no SIM card is inserted, or a
* foreign SIM card is inserted.</li>
* <li>SIM card. This is only an heuristic and assumes the user is not
* roaming.</li>
* <li>User locale. This is an even worse heuristic.</li>
* </ul>
*
* Note: this is very similar to <a href="https://android.googlesource.com/platform/frameworks/base/+/cd92588%5E/location/java/android/location/CountryDetector.java">
* this API</a> except it seems that Google doesn't want us to use it for
* some reason - both that class and {@code Context.COUNTRY_CODE} are
* annotated {@code @hide}.
*/
@Override
@SuppressLint("DefaultLocale")
public String getCurrentCountry() {
String countryCode = getCountryFromPhoneNetwork();
if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
LOG.info("Falling back to SIM card country");
countryCode = getCountryFromSimCard();
if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase();
LOG.info("Falling back to user-defined locale");
return Locale.getDefault().getCountry();
}
private String getCountryFromPhoneNetwork() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o;
return tm == null ? "" : tm.getNetworkCountryIso();
}
private String getCountryFromSimCard() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o;
return tm == null ? "" : tm.getSimCountryIso();
}
}

View File

@@ -31,8 +31,6 @@ import static android.provider.Settings.Secure.ANDROID_ID;
@NotNullByDefault @NotNullByDefault
class AndroidSecureRandomProvider extends UnixSecureRandomProvider { class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
private static final int SEED_LENGTH = 32;
private final Context appContext; private final Context appContext;
@Inject @Inject
@@ -72,27 +70,6 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
// Silence strict mode // Silence strict mode
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites(); StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites();
super.writeSeed(); super.writeSeed();
if (SDK_INT <= 18) applyOpenSslFix();
StrictMode.setThreadPolicy(tp); StrictMode.setThreadPolicy(tp);
} }
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void applyOpenSslFix() {
byte[] seed = new UnixSecureRandomSpi().engineGenerateSeed(
SEED_LENGTH);
try {
// Seed the OpenSSL PRNG
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class)
.invoke(null, (Object) 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);
}
}
} }

View File

@@ -1,12 +1,14 @@
package org.briarproject.bramble.system; package org.briarproject.bramble.system;
import android.app.Application;
import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider; import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.onionwrapper.AndroidLocationUtilsFactory;
import org.briarproject.onionwrapper.LocationUtils;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.RejectedExecutionHandler;
@@ -46,8 +48,9 @@ public class AndroidSystemModule {
} }
@Provides @Provides
LocationUtils provideLocationUtils(AndroidLocationUtils locationUtils) { @Singleton
return locationUtils; LocationUtils provideLocationUtils(Application app) {
return AndroidLocationUtilsFactory.createAndroidLocationUtils(app);
} }
@Provides @Provides
@@ -69,11 +72,4 @@ public class AndroidSystemModule {
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) { ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
return provider; return provider;
} }
@Provides
@Singleton
AndroidWakeLockManager provideWakeLockManager(
AndroidWakeLockManagerImpl wakeLockManager) {
return wakeLockManager;
}
} }

View File

@@ -8,10 +8,10 @@ import android.content.Intent;
import android.os.Process; import android.os.Process;
import android.os.SystemClock; import android.os.SystemClock;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.Cancellable; import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.system.AlarmListener; import org.briarproject.bramble.api.system.AlarmListener;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.Wakeful; import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;

View File

@@ -2,9 +2,9 @@ package org.briarproject.bramble.system;
import android.app.Application; import android.app.Application;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.AlarmListener; import org.briarproject.bramble.api.system.AlarmListener;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;

View File

@@ -1,74 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.FINE;
import static java.util.logging.Logger.getLogger;
/**
* A wrapper around a {@link SharedWakeLock} that provides the more convenient
* semantics of {@link AndroidWakeLock} (i.e. calls to acquire() and release()
* don't need to be balanced).
*/
@ThreadSafe
@NotNullByDefault
class AndroidWakeLockImpl implements AndroidWakeLock {
private static final Logger LOG =
getLogger(AndroidWakeLockImpl.class.getName());
private static final AtomicInteger INSTANCE_ID = new AtomicInteger(0);
private final SharedWakeLock sharedWakeLock;
private final String tag;
private final Object lock = new Object();
@GuardedBy("lock")
private boolean held = false;
AndroidWakeLockImpl(SharedWakeLock sharedWakeLock, String tag) {
this.sharedWakeLock = sharedWakeLock;
this.tag = tag + "_" + INSTANCE_ID.getAndIncrement();
}
@Override
public void acquire() {
synchronized (lock) {
if (held) {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " already acquired");
}
} else {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " acquiring shared wake lock");
}
held = true;
sharedWakeLock.acquire();
}
}
}
@Override
public void release() {
synchronized (lock) {
if (held) {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " releasing shared wake lock");
}
held = false;
sharedWakeLock.release();
} else {
if (LOG.isLoggable(FINE)) {
LOG.fine(tag + " already released");
}
}
}
}
}

View File

@@ -1,125 +0,0 @@
package org.briarproject.bramble.system;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.PowerManager;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Inject;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@NotNullByDefault
class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
/**
* How often to replace the wake lock.
*/
private static final long LOCK_DURATION_MS = MINUTES.toMillis(1);
/**
* Automatically release the lock this many milliseconds after it's due
* to have been replaced and released.
*/
private static final long SAFETY_MARGIN_MS = SECONDS.toMillis(30);
private final SharedWakeLock sharedWakeLock;
@Inject
AndroidWakeLockManagerImpl(Application app,
ScheduledExecutorService scheduledExecutorService) {
PowerManager powerManager = (PowerManager)
requireNonNull(app.getSystemService(POWER_SERVICE));
String tag = getWakeLockTag(app);
sharedWakeLock = new RenewableWakeLock(powerManager,
scheduledExecutorService, PARTIAL_WAKE_LOCK, tag,
LOCK_DURATION_MS, SAFETY_MARGIN_MS);
}
@Override
public AndroidWakeLock createWakeLock(String tag) {
return new AndroidWakeLockImpl(sharedWakeLock, tag);
}
@Override
public void runWakefully(Runnable r, String tag) {
AndroidWakeLock wakeLock = createWakeLock(tag);
wakeLock.acquire();
try {
r.run();
} finally {
wakeLock.release();
}
}
@Override
public void executeWakefully(Runnable r, Executor executor, String tag) {
AndroidWakeLock wakeLock = createWakeLock(tag);
wakeLock.acquire();
try {
executor.execute(() -> {
try {
r.run();
} finally {
// Release the wake lock if the task throws an exception
wakeLock.release();
}
});
} catch (Exception e) {
// Release the wake lock if the executor throws an exception when
// we submit the task (in which case the release() call above won't
// happen)
wakeLock.release();
throw e;
}
}
@Override
public void executeWakefully(Runnable r, String tag) {
AndroidWakeLock wakeLock = createWakeLock(tag);
wakeLock.acquire();
try {
new Thread(() -> {
try {
r.run();
} finally {
wakeLock.release();
}
}).start();
} catch (Exception e) {
wakeLock.release();
throw e;
}
}
private String getWakeLockTag(Context ctx) {
PackageManager pm = ctx.getPackageManager();
if (isInstalled(pm, "com.huawei.powergenie")) {
return "LocationManagerService";
} else if (isInstalled(pm, "com.evenwell.PowerMonitor")) {
return "AudioIn";
}
return ctx.getPackageName();
}
private boolean isInstalled(PackageManager pm, String packageName) {
try {
pm.getPackageInfo(packageName, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.system;
import android.app.Application;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManagerFactory;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AndroidWakeLockModule {
@Provides
@Singleton
AndroidWakeLockManager provideWakeLockManager(Application app,
ScheduledExecutorService scheduledExecutorService) {
return AndroidWakeLockManagerFactory.createAndroidWakeLockManager(app,
scheduledExecutorService);
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.system; package org.briarproject.bramble.system;
import org.briarproject.android.dontkillmelib.wakelock.AndroidWakeLockManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.WakefulIoExecutor; import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;

View File

@@ -1,130 +0,0 @@
package org.briarproject.bramble.system;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@ThreadSafe
@NotNullByDefault
class RenewableWakeLock implements SharedWakeLock {
private static final Logger LOG =
getLogger(RenewableWakeLock.class.getName());
private final PowerManager powerManager;
private final ScheduledExecutorService scheduledExecutorService;
private final int levelAndFlags;
private final String tag;
private final long durationMs, safetyMarginMs;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private WakeLock wakeLock;
@GuardedBy("lock")
@Nullable
private Future<?> future;
@GuardedBy("lock")
private int refCount = 0;
@GuardedBy("lock")
private long acquired = 0;
RenewableWakeLock(PowerManager powerManager,
ScheduledExecutorService scheduledExecutorService,
int levelAndFlags,
String tag,
long durationMs,
long safetyMarginMs) {
this.powerManager = powerManager;
this.scheduledExecutorService = scheduledExecutorService;
this.levelAndFlags = levelAndFlags;
this.tag = tag;
this.durationMs = durationMs;
this.safetyMarginMs = safetyMarginMs;
}
@Override
public void acquire() {
synchronized (lock) {
refCount++;
if (refCount == 1) {
if (LOG.isLoggable(INFO)) {
LOG.info("Acquiring wake lock " + tag);
}
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
// We do our own reference counting so we can replace the lock
// TODO: Check whether using a ref-counted wake lock affects
// power management apps
wakeLock.setReferenceCounted(false);
wakeLock.acquire(durationMs + safetyMarginMs);
future = scheduledExecutorService.schedule(this::renew,
durationMs, MILLISECONDS);
acquired = android.os.SystemClock.elapsedRealtime();
} else if (LOG.isLoggable(FINE)) {
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
}
}
}
private void renew() {
if (LOG.isLoggable(INFO)) LOG.info("Renewing wake lock " + tag);
synchronized (lock) {
if (wakeLock == null) {
LOG.info("Already released");
return;
}
if (LOG.isLoggable(FINE)) {
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
}
long now = android.os.SystemClock.elapsedRealtime();
long expiry = acquired + durationMs + safetyMarginMs;
if (now > expiry && LOG.isLoggable(WARNING)) {
LOG.warning("Wake lock expired " + (now - expiry) + " ms ago");
}
WakeLock oldWakeLock = wakeLock;
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
wakeLock.setReferenceCounted(false);
wakeLock.acquire(durationMs + safetyMarginMs);
oldWakeLock.release();
future = scheduledExecutorService.schedule(this::renew, durationMs,
MILLISECONDS);
acquired = now;
}
}
@Override
public void release() {
synchronized (lock) {
refCount--;
if (refCount == 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Releasing wake lock " + tag);
}
requireNonNull(future).cancel(false);
future = null;
requireNonNull(wakeLock).release();
wakeLock = null;
acquired = 0;
} else if (LOG.isLoggable(FINE)) {
LOG.fine("Wake lock " + tag + " has " + refCount + " holders");
}
}
}
}

View File

@@ -1,22 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SharedWakeLock {
/**
* Acquires the wake lock. This increments the wake lock's reference count,
* so unlike {@link AndroidWakeLock#acquire()} every call to this method
* must be followed by a balancing call to {@link #release()}.
*/
void acquire();
/**
* Releases the wake lock. This decrements the wake lock's reference count,
* so unlike {@link AndroidWakeLock#release()} every call to this method
* must follow a balancing call to {@link #acquire()}.
*/
void release();
}

View File

@@ -11,14 +11,10 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Scanner;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -29,7 +25,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.Process.myPid; import static android.os.Process.myPid;
import static android.os.Process.myUid; import static android.os.Process.myUid;
import static java.lang.Runtime.getRuntime;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.briarproject.nullsafety.NullSafety.requireNonNull; import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@@ -43,14 +38,7 @@ public class AndroidUtils {
private static final String STORED_LOGCAT = "dev-logcat"; private static final String STORED_LOGCAT = "dev-logcat";
public static Collection<String> getSupportedArchitectures() { public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>(); return asList(Build.SUPPORTED_ABIS);
if (SDK_INT >= 21) {
abis.addAll(asList(Build.SUPPORTED_ABIS));
} else {
abis.add(Build.CPU_ABI);
if (Build.CPU_ABI2 != null) abis.add(Build.CPU_ABI2);
}
return abis;
} }
public static boolean hasBtConnectPermission(Context ctx) { public static boolean hasBtConnectPermission(Context ctx) {
@@ -75,11 +63,13 @@ public class AndroidUtils {
return new Pair<>(address, "adapter"); return new Pair<>(address, "adapter");
} }
// Return the address from settings if it's valid and not fake // Return the address from settings if it's valid and not fake
if (SDK_INT < 33) {
address = Settings.Secure.getString(ctx.getContentResolver(), address = Settings.Secure.getString(ctx.getContentResolver(),
"bluetooth_address"); "bluetooth_address");
if (isValidBluetoothAddress(address)) { if (isValidBluetoothAddress(address)) {
return new Pair<>(address, "settings"); return new Pair<>(address, "settings");
} }
}
// Try to get the address via reflection // Try to get the address via reflection
address = getBluetoothAddressByReflection(adapter); address = getBluetoothAddressByReflection(adapter);
if (isValidBluetoothAddress(address)) { if (isValidBluetoothAddress(address)) {
@@ -136,19 +126,6 @@ public class AndroidUtils {
return new String[] {"image/jpeg", "image/png", "image/gif"}; return new String[] {"image/jpeg", "image/png", "image/gif"};
} }
@Nullable
public static String getSystemProperty(String propName) {
try {
Process p = getRuntime().exec("getprop " + propName);
Scanner s = new Scanner(p.getInputStream());
String line = s.nextLine();
s.close();
return line;
} catch (SecurityException | IOException e) {
return null;
}
}
public static boolean isUiThread() { public static boolean isUiThread() {
return Looper.myLooper() == Looper.getMainLooper(); return Looper.myLooper() == Looper.getMainLooper();
} }

View File

@@ -87,6 +87,10 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
File potatoFile = new File(potatoDir, "file"); File potatoFile = new File(potatoDir, "file");
File filesDir = new File(testDir, "filesDir"); File filesDir = new File(testDir, "filesDir");
File externalCacheDir = new File(testDir, "externalCacheDir"); File externalCacheDir = new File(testDir, "externalCacheDir");
File externalCacheDir1 = new File(testDir, "externalCacheDir1");
File externalCacheDir2 = new File(testDir, "externalCacheDir2");
File externalMediaDir1 = new File(testDir, "externalMediaDir1");
File externalMediaDir2 = new File(testDir, "externalMediaDir2");
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(prefs).edit(); oneOf(prefs).edit();
@@ -109,6 +113,12 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
will(returnValue(cacheDir)); will(returnValue(cacheDir));
oneOf(app).getExternalCacheDir(); oneOf(app).getExternalCacheDir();
will(returnValue(externalCacheDir)); will(returnValue(externalCacheDir));
oneOf(app).getExternalCacheDirs();
will(returnValue(
new File[] {externalCacheDir1, externalCacheDir2}));
oneOf(app).getExternalMediaDirs();
will(returnValue(
new File[] {externalMediaDir1, externalMediaDir2}));
}}); }});
assertTrue(dbDir.mkdirs()); assertTrue(dbDir.mkdirs());
@@ -125,6 +135,10 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertTrue(potatoFile.createNewFile()); assertTrue(potatoFile.createNewFile());
assertTrue(filesDir.mkdirs()); assertTrue(filesDir.mkdirs());
assertTrue(externalCacheDir.mkdirs()); assertTrue(externalCacheDir.mkdirs());
assertTrue(externalCacheDir1.mkdirs());
assertTrue(externalCacheDir2.mkdirs());
assertTrue(externalMediaDir1.mkdirs());
assertTrue(externalMediaDir2.mkdirs());
accountManager.deleteAccount(); accountManager.deleteAccount();
@@ -142,6 +156,10 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(potatoFile.exists()); assertFalse(potatoFile.exists());
assertFalse(filesDir.exists()); assertFalse(filesDir.exists());
assertFalse(externalCacheDir.exists()); assertFalse(externalCacheDir.exists());
assertFalse(externalCacheDir1.exists());
assertFalse(externalCacheDir2.exists());
assertFalse(externalMediaDir1.exists());
assertFalse(externalMediaDir2.exists());
} }
@After @After

View File

@@ -4,10 +4,10 @@ dependencyVerification {
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db', 'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79', 'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger-compiler:2.43.2:dagger-compiler-2.43.2.jar:298c020ee6ed2f4cc651ebbfdb7f8de329b07c44b618d65be117846a850e2a03', 'com.google.dagger:dagger-compiler:2.45:dagger-compiler-2.45.jar:5617dfb994537dba5b41f3744a6dd13ec3cd99789c065e0d5c6fa9f21cf7ca25',
'com.google.dagger:dagger-producers:2.43.2:dagger-producers-2.43.2.jar:e7f5d9ffc85d48a49c8e22e02833d418f7ccad5d7512f529964db5127ab915ff', 'com.google.dagger:dagger-producers:2.45:dagger-producers-2.45.jar:a05abb4c3ccf6bb0f056bdcb5ef973898ecf172952ab5948a824aeea6c86ecaa',
'com.google.dagger:dagger-spi:2.43.2:dagger-spi-2.43.2.jar:3bae8d9dadeaaa5927da6f094389a560c12c05fec3d2711d2fa79292c7a7d7ad', 'com.google.dagger:dagger-spi:2.45:dagger-spi-2.45.jar:7cd6f0b09d88e64a9c97bc80e544ab8ac8fdee9301754413585a74cf64222b27',
'com.google.dagger:dagger:2.43.2:dagger-2.43.2.jar:c89681f7cbbf8c527bf4ac2748515d617fdb54a1d425c08d914fdc28192b5fe4', 'com.google.dagger:dagger:2.45:dagger-2.45.jar:f011cae7d2c0fb7ea17c34e05bc10e768b1081a5892ad019cf1fdb0e125c49c1',
'com.google.devtools.ksp:symbol-processing-api:1.7.0-1.0.6:symbol-processing-api-1.7.0-1.0.6.jar:adc29417be5ca9ff42118105fea4e36d9ef44987abfc41432309371a60198941', 'com.google.devtools.ksp:symbol-processing-api:1.7.0-1.0.6:symbol-processing-api-1.7.0-1.0.6.jar:adc29417be5ca9ff42118105fea4e36d9ef44987abfc41432309371a60198941',
'com.google.errorprone:error_prone_annotations:2.7.1:error_prone_annotations-2.7.1.jar:cd5257c08a246cf8628817ae71cb822be192ef91f6881ca4a3fcff4f1de1cff3', 'com.google.errorprone:error_prone_annotations:2.7.1:error_prone_annotations-2.7.1.jar:cd5257c08a246cf8628817ae71cb822be192ef91f6881ca4a3fcff4f1de1cff3',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30', 'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
@@ -17,6 +17,8 @@ dependencyVerification {
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99', 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b', 'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291', 'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:kotlinpoet:1.11.0:kotlinpoet-1.11.0.jar:2887ada1ca03dd83baa2758640d87e840d1907564db0ef88d2289c868a980492',
'io.github.willena:sqlite-jdbc:3.41.2.1:sqlite-jdbc-3.41.2.1.jar:fb60e7137c1791db89240701338d31ca42a0bec5508c1aab1c1131cf885f2309',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
@@ -24,24 +26,31 @@ dependencyVerification {
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0', 'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', 'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy-android:0.0.14-tor1:obfs4proxy-android-0.0.14-tor1.jar:8b08068778b133484b17956d8f7a7710739c33f671a26a68156f4d34e6f28c30', 'org.briarproject:dont-kill-me-lib:0.2.7:dont-kill-me-lib-0.2.7.aar:8a9540941fd927e1c127096a7a9b4aa61ce2f2965d2e24f849be92f9e57213c4',
'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293', 'org.briarproject:jtorctl:0.5:jtorctl-0.5.jar:43f8c7d390169772b9a2c82ab806c8414c136a2a8636c555e22754bb7260793b',
'org.briarproject:tor-android:0.4.7.13:tor-android-0.4.7.13.jar:7852aab7d2298b80878c7719f34ce665725b494d673ecf2e6f9e697564638cc6', 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.briarproject:obfs4proxy-android:0.0.14-tor2:obfs4proxy-android-0.0.14-tor2.jar:a0a93770d6760ce57d9dbd31cc7177687374e00c3361dac22ab75e3b6e0f289e',
'org.briarproject:onionwrapper-android:0.0.5:onionwrapper-android-0.0.5.aar:d761854dac454616b3e0ca099b2cd17060365ce4316afe495cc7ae86b6c81d15',
'org.briarproject:onionwrapper-core:0.0.5:onionwrapper-core-0.0.5.jar:9071678323535cb3dfe0f3add96066037db43ea024333eba0117c759bcbd8d63',
'org.briarproject:snowflake-android:2.5.1:snowflake-android-2.5.1.jar:88ec81c17b1b6fa884d06839dec0330e328b45c89f88c970a213ce91ca8eac87',
'org.briarproject:tor-android:0.4.7.14:tor-android-0.4.7.14.jar:d39faa3a8abb116136c191c6ebadf8ea0e1f3e4785076d2c66a7b3b0f26988a2',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a', 'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb', 'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050', 'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jacoco:org.jacoco.agent:0.8.7:org.jacoco.agent-0.8.7.jar:9cbcc986e0fbe821a78ff1f8f7d5216f200e5eb124e7f6837d1dc4a77b28b143', 'org.jacoco:org.jacoco.agent:0.8.8:org.jacoco.agent-0.8.8.jar:072ecbd496896623899a696fff12c01c1615f737616d2792e6d0e10cdf8a610d',
'org.jacoco:org.jacoco.ant:0.8.7:org.jacoco.ant-0.8.7.jar:97ca96a382c3f23a44d8eb4c4e6c3742a30cb8005774a76ced0fc4806ce49605', 'org.jacoco:org.jacoco.ant:0.8.8:org.jacoco.ant-0.8.8.jar:02e33bd2c48dc0be67c2fea84d43beececfd400da6797c58153253d4c30aca15',
'org.jacoco:org.jacoco.core:0.8.7:org.jacoco.core-0.8.7.jar:ad7739b5fb5969aa1a8aead3d74ed54dc82ed012f1f10f336bd1b96e71c1a13c', 'org.jacoco:org.jacoco.core:0.8.8:org.jacoco.core-0.8.8.jar:474c782f809d88924713dfdbf0acb79d330f904be576484803463d0465611643',
'org.jacoco:org.jacoco.report:0.8.7:org.jacoco.report-0.8.7.jar:cc89258623700a6c932592153cb528785876b6da183d5431f97efbba6f020e5b', 'org.jacoco:org.jacoco.report:0.8.8:org.jacoco.report-0.8.8.jar:2c129110f3e3fcaa1f8179578ea3894586199cb0826be5c7790278084c9622a9',
'org.jetbrains.kotlin:kotlin-reflect:1.6.10:kotlin-reflect-1.6.10.jar:3277ac102ae17aad10a55abec75ff5696c8d109790396434b496e75087854203',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0:kotlin-stdlib-common-1.7.0.jar:59c6ff64fe9a6604afce03e8aaa75f83586c6030ac71fb0b34ee7cdefed3618f',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10:kotlin-stdlib-common-1.7.10.jar:19f102efe9629f8eabc63853ad15c533e47c47f91fca09285c5bde86e59f91d4', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.8.0:kotlin-stdlib-common-1.8.0.jar:78ef93b59e603cc0fe51def9bd4c037b07cbace3b3b7806d1a490a42bc1f4cb2',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239', 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0:kotlin-stdlib-jdk7-1.7.0.jar:07e91be9b2ca20672d2bdb7e181b766e73453a2da13492b5ddaee8fa47aea239',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0:kotlin-stdlib-jdk7-1.8.0.jar:4c889d1d9803f5f2eb6c1592a6b7e62369ac7660c9eee15aba16fec059163666',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b', 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0:kotlin-stdlib-jdk8-1.7.0.jar:cf058e11db1dfc9944680c8c61b95ac689aaaa8a3eb30bced028100f038f030b',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05', 'org.jetbrains.kotlin:kotlin-stdlib:1.7.0:kotlin-stdlib-1.7.0.jar:aa88e9625577957f3249a46cb6e166ee09b369e600f7a11d148d16b0a6d87f05',
'org.jetbrains.kotlin:kotlin-stdlib:1.7.10:kotlin-stdlib-1.7.10.jar:e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901', 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0:kotlin-stdlib-1.8.0.jar:c77bef8774640b9fb9d6e217459ff220dae59878beb7d2e4b430506feffc654e',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4', 'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0:kotlinx-metadata-jvm-0.5.0.jar:ca063a96639b08b9eaa0de4d65e899480740a6efbe28ab9a8681a2ced03055a4',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478', 'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09', 'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
@@ -50,10 +59,10 @@ dependencyVerification {
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04', 'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be', 'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984', 'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-analysis:9.1:asm-analysis-9.1.jar:81a88041b1b8beda5a8a99646098046c48709538270c49def68abff25ac3be34', 'org.ow2.asm:asm-analysis:9.2:asm-analysis-9.2.jar:878fbe521731c072d14d2d65b983b1beae6ad06fda0007b6a8bae81f73f433c4',
'org.ow2.asm:asm-commons:9.1:asm-commons-9.1.jar:afcb26dc1fc12c0c4a99ada670908dd82e18dfc488caf5ee92546996b470c00c', 'org.ow2.asm:asm-commons:9.2:asm-commons-9.2.jar:be4ce53138a238bb522cd781cf91f3ba5ce2f6ca93ec62d46a162a127225e0a6',
'org.ow2.asm:asm-tree:9.1:asm-tree-9.1.jar:fd00afa49e9595d7646205b09cecb4a776a8ff0ba06f2d59b8f7bf9c704b4a73', 'org.ow2.asm:asm-tree:9.2:asm-tree-9.2.jar:aabf9bd23091a4ebfc109c1f3ee7cf3e4b89f6ba2d3f51c5243f16b3cffae011',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de', 'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2', 'org.ow2.asm:asm:9.2:asm-9.2.jar:b9d4fe4d71938df38839f0eca42aaaa64cf8b313d678da036f0cb3ca199b47f5',
] ]
} }

View File

@@ -11,8 +11,6 @@ public interface FeatureFlags {
boolean shouldEnableDisappearingMessages(); boolean shouldEnableDisappearingMessages();
boolean shouldEnableMailbox();
boolean shouldEnablePrivateGroupsInCore(); boolean shouldEnablePrivateGroupsInCore();
boolean shouldEnableForumsInCore(); boolean shouldEnableForumsInCore();

View File

@@ -16,6 +16,7 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE; import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
@Immutable @Immutable
@@ -23,17 +24,30 @@ import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOC
public abstract class BdfMessageValidator implements MessageValidator { public abstract class BdfMessageValidator implements MessageValidator {
protected static final Logger LOG = protected static final Logger LOG =
Logger.getLogger(BdfMessageValidator.class.getName()); getLogger(BdfMessageValidator.class.getName());
protected final ClientHelper clientHelper; protected final ClientHelper clientHelper;
protected final MetadataEncoder metadataEncoder; protected final MetadataEncoder metadataEncoder;
protected final Clock clock; protected final Clock clock;
protected final boolean canonical;
/**
* Transitional alternative to
* {@link #BdfMessageValidator(ClientHelper, MetadataEncoder, Clock)} that
* accepts messages in non-canonical form, for backward compatibility.
*/
@Deprecated
protected BdfMessageValidator(ClientHelper clientHelper, protected BdfMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) { MetadataEncoder metadataEncoder, Clock clock, boolean canonical) {
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
this.clock = clock; this.clock = clock;
this.canonical = canonical;
}
protected BdfMessageValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
this(clientHelper, metadataEncoder, clock, true);
} }
protected abstract BdfMessageContext validateMessage(Message m, Group g, protected abstract BdfMessageContext validateMessage(Message m, Group g,
@@ -49,7 +63,7 @@ public abstract class BdfMessageValidator implements MessageValidator {
"Timestamp is too far in the future"); "Timestamp is too far in the future");
} }
try { try {
BdfList bodyList = clientHelper.toList(m.getBody()); BdfList bodyList = clientHelper.toList(m, canonical);
BdfMessageContext result = validateMessage(m, g, bodyList); BdfMessageContext result = validateMessage(m, g, bodyList);
Metadata meta = metadataEncoder.encode(result.getDictionary()); Metadata meta = metadataEncoder.encode(result.getDictionary());
return new MessageContext(meta, result.getDependencies()); return new MessageContext(meta, result.getDependencies());

View File

@@ -49,6 +49,18 @@ public interface ClientHelper {
BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException, BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException,
FormatException; FormatException;
/**
* Transitional alternative to
* {@link #getMessageAsList(Transaction, MessageId)} that allows the
* message to be in non-canonical form, for backward compatibility.
*
* @param canonical True if the message must be in canonical form (a
* {@link FormatException} will be thrown if it's not.
*/
@Deprecated
BdfList getMessageAsList(Transaction txn, MessageId m, boolean canonical)
throws DbException, FormatException;
BdfDictionary getGroupMetadataAsDictionary(GroupId g) throws DbException, BdfDictionary getGroupMetadataAsDictionary(GroupId g) throws DbException,
FormatException; FormatException;
@@ -106,6 +118,16 @@ public interface ClientHelper {
BdfList toList(Message m) throws FormatException; BdfList toList(Message m) throws FormatException;
/**
* Transitional alternative to {@link #toList(Message)} that allows the
* message to be in non-canonical form, for backward compatibility.
*
* @param canonical True if the message must be in canonical form (a
* {@link FormatException} will be thrown if it's not.
*/
@Deprecated
BdfList toList(Message m, boolean canonical) throws FormatException;
BdfList toList(Author a); BdfList toList(Author a);
byte[] sign(String label, BdfList toSign, PrivateKey privateKey) byte[] sign(String label, BdfList toSign, PrivateKey privateKey)

View File

@@ -54,6 +54,38 @@ public interface CryptoComponent {
KeyPair ourKeyPair, byte[]... inputs) KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException; throws GeneralSecurityException;
/**
* Derives a shared secret from two static and two ephemeral key pairs.
* <p>
* Do not use this method for new protocols. The shared secret can be
* re-derived using the ephemeral public keys and both static private
* keys, so keys derived from the shared secret should not be used if
* forward secrecy is required. Use {@link #deriveSharedSecret(String,
* PublicKey, PublicKey, KeyPair, KeyPair, boolean, byte[]...)} instead.
* <p>
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
* <p>
*
* @param label A namespaced label indicating the purpose of this shared
* secret, to prevent it from being repurposed or colliding with a shared
* secret derived for another purpose
* @param theirStaticPublicKey The static public key of the remote party
* @param theirEphemeralPublicKey The ephemeral public key of the remote
* party
* @param ourStaticKeyPair The static key pair of the local party
* @param ourEphemeralKeyPair The ephemeral key pair of the local party
* @param alice True if the local party is Alice
* @param inputs Additional inputs that will be included in the
* derivation of the shared secret
* @return The shared secret
*/
@Deprecated
SecretKey deriveSharedSecretBadly(String label,
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
boolean alice, byte[]... inputs)
throws GeneralSecurityException;
/** /**
* Derives a shared secret from two static and two ephemeral key pairs. * Derives a shared secret from two static and two ephemeral key pairs.
* *

View File

@@ -10,8 +10,29 @@ import java.util.TreeMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
/**
* A BDF dictionary contains zero or more key-value pairs, where the keys
* are strings and the values are BDF objects, which may be primitive types
* (null, boolean, integer, float, string, raw) or nested containers (list,
* dictionary).
* <p>
* Note that a BDF integer has the same range as a Java long, while a BDF
* float has the same range as a Java double. Method names in this class
* correspond to the Java types.
* <p>
* The getX() methods throw {@link FormatException} if the specified key is
* absent, the value is null, or the value does not have the requested type.
* <p>
* The getOptionalX() methods return null if the specified key is absent or
* the value is null, or throw {@link FormatException} if the value does not
* have the requested type.
* <p>
* The getX() methods that take a default value return the default value if
* the specified key is absent or the value is null, or throw
* {@link FormatException} if the value does not have the requested type.
*/
@NotThreadSafe @NotThreadSafe
public class BdfDictionary extends TreeMap<String, Object> { public final class BdfDictionary extends TreeMap<String, Object> {
public static final Object NULL_VALUE = new Object(); public static final Object NULL_VALUE = new Object();
@@ -39,9 +60,9 @@ public class BdfDictionary extends TreeMap<String, Object> {
} }
public Boolean getBoolean(String key) throws FormatException { public Boolean getBoolean(String key) throws FormatException {
Object o = get(key); Boolean value = getOptionalBoolean(key);
if (o instanceof Boolean) return (Boolean) o; if (value == null) throw new FormatException();
throw new FormatException(); return value;
} }
@Nullable @Nullable
@@ -52,19 +73,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException(); throw new FormatException();
} }
public Boolean getBoolean(String key, Boolean defaultValue) { public Boolean getBoolean(String key, Boolean defaultValue)
Object o = get(key); throws FormatException {
if (o instanceof Boolean) return (Boolean) o; Boolean value = getOptionalBoolean(key);
return defaultValue; return value == null ? defaultValue : value;
} }
public Long getLong(String key) throws FormatException { public Long getLong(String key) throws FormatException {
Object o = get(key); Long value = getOptionalLong(key);
if (o instanceof Long) return (Long) o; if (value == null) throw new FormatException();
if (o instanceof Integer) return ((Integer) o).longValue(); return value;
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
throw new FormatException();
} }
@Nullable @Nullable
@@ -78,20 +96,69 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException(); throw new FormatException();
} }
public Long getLong(String key, Long defaultValue) { public Long getLong(String key, Long defaultValue) throws FormatException {
Object o = get(key); Long value = getOptionalLong(key);
if (o instanceof Long) return (Long) o; return value == null ? defaultValue : value;
if (o instanceof Integer) return ((Integer) o).longValue(); }
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue(); /**
return defaultValue; * Returns the integer with the specified key.
* <p>
* This method should be used in preference to
* <code>getLong(key).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if there is no value at the specified key,
* or if the value is null or cannot be represented as a Java int.
*/
public Integer getInt(String key) throws FormatException {
Integer value = getOptionalInt(key);
if (value == null) throw new FormatException();
return value;
}
/**
* Returns the integer with the specified key, or null if the key is
* absent or the value is null.
* <p>
* This method should be used in preference to
* <code>getOptionalLong(key).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the value at the specified key is not null
* and cannot be represented as a Java int.
*/
@Nullable
public Integer getOptionalInt(String key) throws FormatException {
Long value = getOptionalLong(key);
if (value == null) return null;
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
throw new FormatException();
}
return value.intValue();
}
/**
* Returns the integer with the specified key, or the given default
* value if the key is absent or the value is null.
* <p>
* This method should be used in preference to
* <code>getLong(key, defaultValue).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the value at the specified key is not null
* and cannot be represented as a Java int.
*/
public Integer getInt(String key, Integer defaultValue)
throws FormatException {
Integer value = getOptionalInt(key);
return value == null ? defaultValue : value;
} }
public Double getDouble(String key) throws FormatException { public Double getDouble(String key) throws FormatException {
Object o = get(key); Double value = getOptionalDouble(key);
if (o instanceof Double) return (Double) o; if (value == null) throw new FormatException();
if (o instanceof Float) return ((Float) o).doubleValue(); return value;
throw new FormatException();
} }
@Nullable @Nullable
@@ -103,17 +170,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException(); throw new FormatException();
} }
public Double getDouble(String key, Double defaultValue) { public Double getDouble(String key, Double defaultValue)
Object o = get(key); throws FormatException {
if (o instanceof Double) return (Double) o; Double value = getOptionalDouble(key);
if (o instanceof Float) return ((Float) o).doubleValue(); return value == null ? defaultValue : value;
return defaultValue;
} }
public String getString(String key) throws FormatException { public String getString(String key) throws FormatException {
Object o = get(key); String value = getOptionalString(key);
if (o instanceof String) return (String) o; if (value == null) throw new FormatException();
throw new FormatException(); return value;
} }
@Nullable @Nullable
@@ -124,17 +190,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException(); throw new FormatException();
} }
public String getString(String key, String defaultValue) { public String getString(String key, String defaultValue)
Object o = get(key); throws FormatException {
if (o instanceof String) return (String) o; String value = getOptionalString(key);
return defaultValue; return value == null ? defaultValue : value;
} }
public byte[] getRaw(String key) throws FormatException { public byte[] getRaw(String key) throws FormatException {
Object o = get(key); byte[] value = getOptionalRaw(key);
if (o instanceof byte[]) return (byte[]) o; if (value == null) throw new FormatException();
if (o instanceof Bytes) return ((Bytes) o).getBytes(); return value;
throw new FormatException();
} }
@Nullable @Nullable
@@ -146,17 +211,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException(); throw new FormatException();
} }
public byte[] getRaw(String key, byte[] defaultValue) { public byte[] getRaw(String key, byte[] defaultValue)
Object o = get(key); throws FormatException {
if (o instanceof byte[]) return (byte[]) o; byte[] value = getOptionalRaw(key);
if (o instanceof Bytes) return ((Bytes) o).getBytes(); return value == null ? defaultValue : value;
return defaultValue;
} }
public BdfList getList(String key) throws FormatException { public BdfList getList(String key) throws FormatException {
Object o = get(key); BdfList value = getOptionalList(key);
if (o instanceof BdfList) return (BdfList) o; if (value == null) throw new FormatException();
throw new FormatException(); return value;
} }
@Nullable @Nullable
@@ -167,16 +231,16 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException(); throw new FormatException();
} }
public BdfList getList(String key, BdfList defaultValue) { public BdfList getList(String key, BdfList defaultValue)
Object o = get(key); throws FormatException {
if (o instanceof BdfList) return (BdfList) o; BdfList value = getOptionalList(key);
return defaultValue; return value == null ? defaultValue : value;
} }
public BdfDictionary getDictionary(String key) throws FormatException { public BdfDictionary getDictionary(String key) throws FormatException {
Object o = get(key); BdfDictionary value = getOptionalDictionary(key);
if (o instanceof BdfDictionary) return (BdfDictionary) o; if (value == null) throw new FormatException();
throw new FormatException(); return value;
} }
@Nullable @Nullable
@@ -188,9 +252,9 @@ public class BdfDictionary extends TreeMap<String, Object> {
throw new FormatException(); throw new FormatException();
} }
public BdfDictionary getDictionary(String key, BdfDictionary defaultValue) { public BdfDictionary getDictionary(String key, BdfDictionary defaultValue)
Object o = get(key); throws FormatException {
if (o instanceof BdfDictionary) return (BdfDictionary) o; BdfDictionary value = getOptionalDictionary(key);
return defaultValue; return value == null ? defaultValue : value;
} }
} }

View File

@@ -6,6 +6,11 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/**
* A convenience class for building {@link BdfDictionary BdfDictionaries}
* via the {@link BdfDictionary#of(Entry[]) factory method}. Entries in
* BdfDictionaries do not have to be BdfEntries.
*/
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class BdfEntry implements Entry<String, Object>, Comparable<BdfEntry> { public class BdfEntry implements Entry<String, Object>, Comparable<BdfEntry> {

View File

@@ -12,8 +12,31 @@ import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
/**
* A BDF list contains zero or more BDF objects, which may be primitive types
* (null, boolean, integer, float, string, raw) or nested containers (list,
* dictionary).
* <p>
* Note that a BDF integer has the same range as a Java long, while a BDF
* float has the same range as a Java double. Method names in this class
* correspond to the Java types.
* <p>
* The getX() methods throw {@link FormatException} if the object at the
* specified index is null or does not have the requested type.
* <p>
* The getOptionalX() methods return null if the object at the specified
* index is null, or throw {@link FormatException} if the object does not
* have the requested type.
* <p>
* The getX() methods that take a default value return the default value if
* the object at the specified index is null, or throw
* {@link FormatException} if the object does not have the requested type.
* <p>
* All of the getters throw {@link FormatException} if the specified index is
* out of range.
*/
@NotThreadSafe @NotThreadSafe
public class BdfList extends ArrayList<Object> { public final class BdfList extends ArrayList<Object> {
/** /**
* Factory method for constructing lists inline. * Factory method for constructing lists inline.
@@ -33,15 +56,15 @@ public class BdfList extends ArrayList<Object> {
super(items); super(items);
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isInRange(int index) { private boolean isInRange(int index) {
return index >= 0 && index < size(); return index >= 0 && index < size();
} }
public Boolean getBoolean(int index) throws FormatException { public Boolean getBoolean(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException(); Boolean value = getOptionalBoolean(index);
Object o = get(index); if (value == null) throw new FormatException();
if (o instanceof Boolean) return (Boolean) o; return value;
throw new FormatException();
} }
@Nullable @Nullable
@@ -53,21 +76,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException(); throw new FormatException();
} }
public Boolean getBoolean(int index, Boolean defaultValue) { public Boolean getBoolean(int index, Boolean defaultValue)
if (!isInRange(index)) return defaultValue; throws FormatException {
Object o = get(index); Boolean value = getOptionalBoolean(index);
if (o instanceof Boolean) return (Boolean) o; return value == null ? defaultValue : value;
return defaultValue;
} }
public Long getLong(int index) throws FormatException { public Long getLong(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException(); Long value = getOptionalLong(index);
Object o = get(index); if (value == null) throw new FormatException();
if (o instanceof Long) return (Long) o; return value;
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue();
if (o instanceof Byte) return ((Byte) o).longValue();
throw new FormatException();
} }
@Nullable @Nullable
@@ -82,22 +100,70 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException(); throw new FormatException();
} }
public Long getLong(int index, Long defaultValue) { public Long getLong(int index, Long defaultValue) throws FormatException {
if (!isInRange(index)) return defaultValue; Long value = getOptionalLong(index);
Object o = get(index); return value == null ? defaultValue : value;
if (o instanceof Long) return (Long) o; }
if (o instanceof Integer) return ((Integer) o).longValue();
if (o instanceof Short) return ((Short) o).longValue(); /**
if (o instanceof Byte) return ((Byte) o).longValue(); * Returns the integer at the specified index.
return defaultValue; * <p>
* This method should be used in preference to
* <code>getLong(index).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the index is out of range, or if the
* value at the specified index is null or cannot be represented as a
* Java int.
*/
public Integer getInt(int index) throws FormatException {
Integer value = getOptionalInt(index);
if (value == null) throw new FormatException();
return value;
}
/**
* Returns the integer at the specified index, or null if the object at
* the specified index is null.
* <p>
* This method should be used in preference to
* <code>getOptionalLong(index).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the index is out of range, or if the value
* at the specified index cannot be represented as a Java int.
*/
@Nullable
public Integer getOptionalInt(int index) throws FormatException {
Long value = getOptionalLong(index);
if (value == null) return null;
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
throw new FormatException();
}
return value.intValue();
}
/**
* Returns the integer at the specified index, or the given default value
* if the object at the specified index is null.
* <p>
* This method should be used in preference to
* <code>getLong(index, defaultValue).intValue()</code> as it checks for
* overflow/underflow.
*
* @throws FormatException if the index is out of range, or if the value
* at the specified index cannot be represented as a Java int.
*/
public Integer getInt(int index, Integer defaultValue)
throws FormatException {
Integer value = getOptionalInt(index);
return value == null ? defaultValue : value;
} }
public Double getDouble(int index) throws FormatException { public Double getDouble(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException(); Double value = getOptionalDouble(index);
Object o = get(index); if (value == null) throw new FormatException();
if (o instanceof Double) return (Double) o; return value;
if (o instanceof Float) return ((Float) o).doubleValue();
throw new FormatException();
} }
@Nullable @Nullable
@@ -110,19 +176,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException(); throw new FormatException();
} }
public Double getDouble(int index, Double defaultValue) { public Double getDouble(int index, Double defaultValue)
if (!isInRange(index)) return defaultValue; throws FormatException {
Object o = get(index); Double value = getOptionalDouble(index);
if (o instanceof Double) return (Double) o; return value == null ? defaultValue : value;
if (o instanceof Float) return ((Float) o).doubleValue();
return defaultValue;
} }
public String getString(int index) throws FormatException { public String getString(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException(); String value = getOptionalString(index);
Object o = get(index); if (value == null) throw new FormatException();
if (o instanceof String) return (String) o; return value;
throw new FormatException();
} }
@Nullable @Nullable
@@ -134,19 +197,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException(); throw new FormatException();
} }
public String getString(int index, String defaultValue) { public String getString(int index, String defaultValue)
if (!isInRange(index)) return defaultValue; throws FormatException {
Object o = get(index); String value = getOptionalString(index);
if (o instanceof String) return (String) o; return value == null ? defaultValue : value;
return defaultValue;
} }
public byte[] getRaw(int index) throws FormatException { public byte[] getRaw(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException(); byte[] value = getOptionalRaw(index);
Object o = get(index); if (value == null) throw new FormatException();
if (o instanceof byte[]) return (byte[]) o; return value;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
throw new FormatException();
} }
@Nullable @Nullable
@@ -159,19 +219,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException(); throw new FormatException();
} }
public byte[] getRaw(int index, byte[] defaultValue) { public byte[] getRaw(int index, byte[] defaultValue)
if (!isInRange(index)) return defaultValue; throws FormatException {
Object o = get(index); byte[] value = getOptionalRaw(index);
if (o instanceof byte[]) return (byte[]) o; return value == null ? defaultValue : value;
if (o instanceof Bytes) return ((Bytes) o).getBytes();
return defaultValue;
} }
public BdfList getList(int index) throws FormatException { public BdfList getList(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException(); BdfList value = getOptionalList(index);
Object o = get(index); if (value == null) throw new FormatException();
if (o instanceof BdfList) return (BdfList) o; return value;
throw new FormatException();
} }
@Nullable @Nullable
@@ -183,18 +240,16 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException(); throw new FormatException();
} }
public BdfList getList(int index, BdfList defaultValue) { public BdfList getList(int index, BdfList defaultValue)
if (!isInRange(index)) return defaultValue; throws FormatException {
Object o = get(index); BdfList value = getOptionalList(index);
if (o instanceof BdfList) return (BdfList) o; return value == null ? defaultValue : value;
return defaultValue;
} }
public BdfDictionary getDictionary(int index) throws FormatException { public BdfDictionary getDictionary(int index) throws FormatException {
if (!isInRange(index)) throw new FormatException(); BdfDictionary value = getOptionalDictionary(index);
Object o = get(index); if (value == null) throw new FormatException();
if (o instanceof BdfDictionary) return (BdfDictionary) o; return value;
throw new FormatException();
} }
@Nullable @Nullable
@@ -207,10 +262,9 @@ public class BdfList extends ArrayList<Object> {
throw new FormatException(); throw new FormatException();
} }
public BdfDictionary getDictionary(int index, BdfDictionary defaultValue) { public BdfDictionary getDictionary(int index, BdfDictionary defaultValue)
if (!isInRange(index)) return defaultValue; throws FormatException {
Object o = get(index); BdfDictionary value = getOptionalDictionary(index);
if (o instanceof BdfDictionary) return (BdfDictionary) o; return value == null ? defaultValue : value;
return defaultValue;
} }
} }

View File

@@ -1,76 +1,178 @@
package org.briarproject.bramble.api.data; package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
/**
* An interface for reading BDF objects from an input stream.
* <p>
* The readX() methods throw {@link FormatException} if the data is not in
* canonical form, but the hasX() and skipX() methods do not check for
* canonical form.
*/
@NotNullByDefault @NotNullByDefault
public interface BdfReader { public interface BdfReader {
int DEFAULT_NESTED_LIMIT = 5; int DEFAULT_NESTED_LIMIT = 5;
int DEFAULT_MAX_BUFFER_SIZE = 64 * 1024; int DEFAULT_MAX_BUFFER_SIZE = 64 * 1024;
/**
* Returns true if the reader has reached the end of its input stream.
*/
boolean eof() throws IOException; boolean eof() throws IOException;
/**
* Closes the reader's input stream.
*/
void close() throws IOException; void close() throws IOException;
/**
* Returns true if the next object in the input is a BDF null.
*/
boolean hasNull() throws IOException; boolean hasNull() throws IOException;
/**
* Reads a BDF null from the input.
*/
void readNull() throws IOException; void readNull() throws IOException;
/**
* Skips over a BDF null.
*/
void skipNull() throws IOException; void skipNull() throws IOException;
/**
* Returns true if the next object in the input is a BDF boolean.
*/
boolean hasBoolean() throws IOException; boolean hasBoolean() throws IOException;
/**
* Reads a BDF boolean from the input and returns it.
*/
boolean readBoolean() throws IOException; boolean readBoolean() throws IOException;
/**
* Skips over a BDF boolean.
*/
void skipBoolean() throws IOException; void skipBoolean() throws IOException;
/**
* Returns true if the next object in the input is a BDF integer, which
* has the same range as a Java long.
*/
boolean hasLong() throws IOException; boolean hasLong() throws IOException;
/**
* Reads a BDF integer from the input and returns it as a Java long.
*/
long readLong() throws IOException; long readLong() throws IOException;
/**
* Skips over a BDF integer.
*/
void skipLong() throws IOException; void skipLong() throws IOException;
/**
* Returns true if the next object in the input is a BDF integer and the
* value would fit within the range of a Java int.
*/
boolean hasInt() throws IOException;
/**
* Reads a BDF integer from the input and returns it as a Java int.
*
* @throws FormatException if the value exceeds the range of a Java int.
*/
int readInt() throws IOException;
/**
* Skips over a BDF integer.
*
* @throws FormatException if the value exceeds the range of a Java int.
*/
void skipInt() throws IOException;
/**
* Returns true if the next object in the input is a BDF float, which has
* the same range as a Java double.
*/
boolean hasDouble() throws IOException; boolean hasDouble() throws IOException;
/**
* Reads a BDF float from the input and returns it as a Java double.
*/
double readDouble() throws IOException; double readDouble() throws IOException;
/**
* Skips over a BDF float.
*/
void skipDouble() throws IOException; void skipDouble() throws IOException;
/**
* Returns true if the next object in the input is a BDF string.
*/
boolean hasString() throws IOException; boolean hasString() throws IOException;
/**
* Reads a BDF string from the input.
*
* @throws IOException If the string is not valid UTF-8.
*/
String readString() throws IOException; String readString() throws IOException;
/**
* Skips over a BDF string without checking whether it is valid UTF-8.
*/
void skipString() throws IOException; void skipString() throws IOException;
/**
* Returns true if the next object in the input is a BDF raw.
*/
boolean hasRaw() throws IOException; boolean hasRaw() throws IOException;
/**
* Reads a BDF raw from the input and returns it as a byte array.
*/
byte[] readRaw() throws IOException; byte[] readRaw() throws IOException;
/**
* Skips over a BDF raw.
*/
void skipRaw() throws IOException; void skipRaw() throws IOException;
/**
* Returns true if the next object in the input is a BDF list.
*/
boolean hasList() throws IOException; boolean hasList() throws IOException;
/**
* Reads a BDF list from the input and returns it. The list's contents
* are parsed and validated.
*/
BdfList readList() throws IOException; BdfList readList() throws IOException;
void readListStart() throws IOException; /**
* Skips over a BDF list. The list's contents are parsed (to determine
boolean hasListEnd() throws IOException; * their length) but not validated.
*/
void readListEnd() throws IOException;
void skipList() throws IOException; void skipList() throws IOException;
/**
* Returns true if the next object in the input is a BDF dictionary.
*/
boolean hasDictionary() throws IOException; boolean hasDictionary() throws IOException;
/**
* Reads a BDF dictionary from the input and returns it. The dictionary's
* contents are parsed and validated.
*/
BdfDictionary readDictionary() throws IOException; BdfDictionary readDictionary() throws IOException;
void readDictionaryStart() throws IOException; /**
* Skips over a BDF dictionary. The dictionary's contents are parsed
boolean hasDictionaryEnd() throws IOException; * (to determine their length) but not validated.
*/
void readDictionaryEnd() throws IOException;
void skipDictionary() throws IOException; void skipDictionary() throws IOException;
} }

View File

@@ -9,6 +9,14 @@ public interface BdfReaderFactory {
BdfReader createReader(InputStream in); BdfReader createReader(InputStream in);
/**
* Transitional alternative to {@link #createReader(InputStream)} that
* can create a reader that accepts non-canonical input, for backward
* compatibility.
*/
@Deprecated
BdfReader createReader(InputStream in, boolean canonical);
BdfReader createReader(InputStream in, int nestedLimit, BdfReader createReader(InputStream in, int nestedLimit,
int maxBufferSize); int maxBufferSize, boolean canonical);
} }

View File

@@ -1,36 +1,74 @@
package org.briarproject.bramble.api.data; package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.FormatException;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
/**
* An interface for writing BDF objects to an output stream. The BDF output
* is in canonical form, ie integers and length fields are represented using
* the minimum number of bytes and dictionary keys are unique and sorted in
* lexicographic order.
*/
public interface BdfWriter { public interface BdfWriter {
/**
* Flushes the writer's output stream.
*/
void flush() throws IOException; void flush() throws IOException;
/**
* Closes the writer's output stream.
*/
void close() throws IOException; void close() throws IOException;
/**
* Writes a BDF null to the output stream.
*/
void writeNull() throws IOException; void writeNull() throws IOException;
/**
* Writes a BDF boolean to the output stream.
*/
void writeBoolean(boolean b) throws IOException; void writeBoolean(boolean b) throws IOException;
/**
* Writes a BDF integer (which has the same range as a Java long) to the
* output stream.
*/
void writeLong(long l) throws IOException; void writeLong(long l) throws IOException;
/**
* Writes a BDF float (which has the same range as a Java double) to the
* output stream.
*/
void writeDouble(double d) throws IOException; void writeDouble(double d) throws IOException;
/**
* Writes a BDF string (which uses UTF-8 encoding) to the output stream.
*/
void writeString(String s) throws IOException; void writeString(String s) throws IOException;
/**
* Writes a BDF raw to the output stream.
*/
void writeRaw(byte[] b) throws IOException; void writeRaw(byte[] b) throws IOException;
/**
* Writes a BDF list to the output stream.
*
* @throws FormatException if the contents of the given collection cannot
* be represented as (nested) BDF objects.
*/
void writeList(Collection<?> c) throws IOException; void writeList(Collection<?> c) throws IOException;
void writeListStart() throws IOException; /**
* Writes a BDF dictionary to the output stream.
void writeListEnd() throws IOException; *
* @throws FormatException if the contents of the given map cannot be
* represented as (nested) BDF objects.
*/
void writeDictionary(Map<?, ?> m) throws IOException; void writeDictionary(Map<?, ?> m) throws IOException;
void writeDictionaryStart() throws IOException;
void writeDictionaryEnd() throws IOException;
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -34,6 +35,16 @@ public interface MailboxManager {
*/ */
MailboxPairingTask startPairingTask(String qrCodePayload); MailboxPairingTask startPairingTask(String qrCodePayload);
/**
* Takes a textual QR code representation in
* {@link org.briarproject.bramble.util.Base32} format and converts it
* into a qrCodePayload as expected by {@link #startPairingTask(String)}.
*
* @throws FormatException when the provided payload did not include a
* proper briar-mailbox:// link.
*/
String convertBase32Payload(String base32Payload) throws FormatException;
/** /**
* Can be used by the UI to test the mailbox connection. * Can be used by the UI to test the mailbox connection.
* *

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.network;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@@ -27,4 +28,20 @@ public class NetworkStatus {
public boolean isIpv6Only() { public boolean isIpv6Only() {
return ipv6Only; return ipv6Only;
} }
@Override
public int hashCode() {
return (connected ? 1 : 0) | (wifi ? 2 : 0) | (ipv6Only ? 4 : 0);
}
@Override
public boolean equals(@Nullable Object o) {
if (o instanceof NetworkStatus) {
NetworkStatus s = (NetworkStatus) o;
return connected == s.connected
&& wifi == s.wifi
&& ipv6Only == s.ipv6Only;
}
return false;
}
} }

View File

@@ -32,8 +32,15 @@ public interface RecordReader {
* 'accept' or 'ignore' predicates * 'accept' or 'ignore' predicates
*/ */
@Nullable @Nullable
Record readRecord(Predicate<Record> accept, Predicate<Record> ignore) Record readRecord(RecordPredicate accept, RecordPredicate ignore)
throws IOException; throws IOException;
void close() throws IOException; void close() throws IOException;
/**
* Interface that reifies the generic interface {@code Predicate<Record>}
* for easier testing.
*/
interface RecordPredicate extends Predicate<Record> {
}
} }

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.api.system;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface LocationUtils {
/**
* Get the country the device is currently located in, or "" if it cannot
* be determined.
* <p>
* The country codes are formatted upper-case and as per <a href="
* https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-1 alpha 2</a>.
*/
String getCurrentCountry();
}

View File

@@ -4,6 +4,8 @@ import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.util.StringUtils.startsWithIgnoreCase;
@NotNullByDefault @NotNullByDefault
public class OsUtils { public class OsUtils {
@@ -13,15 +15,15 @@ public class OsUtils {
private static final String vendor = System.getProperty("java.vendor"); private static final String vendor = System.getProperty("java.vendor");
public static boolean isWindows() { public static boolean isWindows() {
return os != null && os.contains("Windows"); return os != null && startsWithIgnoreCase(os, "Win");
} }
public static boolean isMac() { public static boolean isMac() {
return os != null && os.contains("Mac OS"); return os != null && os.equalsIgnoreCase("Mac OS X");
} }
public static boolean isLinux() { public static boolean isLinux() {
return os != null && os.contains("Linux") && !isAndroid(); return os != null && startsWithIgnoreCase(os, "Linux") && !isAndroid();
} }
public static boolean isAndroid() { public static boolean isAndroid() {

View File

@@ -6,6 +6,7 @@ import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Locale;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -51,7 +52,7 @@ public class PrivacyUtils {
} }
private static String scrubIpv6Address(byte[] ipv6) { private static String scrubIpv6Address(byte[] ipv6) {
String hex = toHexString(ipv6).toLowerCase(); String hex = toHexString(ipv6).toLowerCase(Locale.US);
return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30); return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
} }

View File

@@ -14,6 +14,7 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.nio.charset.CodingErrorAction.IGNORE; import static java.nio.charset.CodingErrorAction.IGNORE;
import static java.nio.charset.CodingErrorAction.REPORT;
import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.CASE_INSENSITIVE;
@SuppressWarnings("CharsetObjectCanBeUsed") @SuppressWarnings("CharsetObjectCanBeUsed")
@@ -52,26 +53,38 @@ public class StringUtils {
return s.getBytes(UTF_8); return s.getBytes(UTF_8);
} }
public static String fromUtf8(byte[] bytes) { public static String fromUtf8(byte[] bytes) throws FormatException {
return fromUtf8(bytes, 0, bytes.length); return fromUtf8(bytes, 0, bytes.length, true);
} }
public static String fromUtf8(byte[] bytes, int off, int len) { public static String fromUtf8(byte[] bytes, int off, int len)
throws FormatException {
return fromUtf8(bytes, off, len, true);
}
private static String fromUtf8(byte[] bytes, int off, int len,
boolean strict) throws FormatException {
CharsetDecoder decoder = UTF_8.newDecoder(); CharsetDecoder decoder = UTF_8.newDecoder();
decoder.onMalformedInput(IGNORE); decoder.onMalformedInput(strict ? REPORT : IGNORE);
decoder.onUnmappableCharacter(IGNORE); decoder.onUnmappableCharacter(strict ? REPORT : IGNORE);
ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len); ByteBuffer buffer = ByteBuffer.wrap(bytes, off, len);
try { try {
return decoder.decode(buffer).toString(); return decoder.decode(buffer).toString();
} catch (CharacterCodingException e) { } catch (CharacterCodingException e) {
throw new AssertionError(e); throw new FormatException();
} }
} }
public static String truncateUtf8(String s, int maxUtf8Length) { public static String truncateUtf8(String s, int maxUtf8Length) {
byte[] utf8 = toUtf8(s); byte[] utf8 = toUtf8(s);
if (utf8.length <= maxUtf8Length) return s; if (utf8.length <= maxUtf8Length) return s;
return fromUtf8(utf8, 0, maxUtf8Length); // Don't be strict when converting back, so that if we truncate a
// multi-byte character the whole character gets dropped
try {
return fromUtf8(utf8, 0, maxUtf8Length, false);
} catch (FormatException e) {
throw new AssertionError(e);
}
} }
/** /**
@@ -163,4 +176,9 @@ public class StringUtils {
} }
return new String(c); return new String(c);
} }
// see https://stackoverflow.com/a/38947571
static boolean startsWithIgnoreCase(String s, String prefix) {
return s.regionMatches(true, 0, prefix, 0, prefix.length());
}
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.data; package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test; import org.junit.Test;
@@ -8,9 +9,12 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map.Entry; import java.util.Map.Entry;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class BdfDictionaryTest extends BrambleTestCase { public class BdfDictionaryTest extends BrambleTestCase {
@@ -19,20 +23,20 @@ public class BdfDictionaryTest extends BrambleTestCase {
public void testConstructors() { public void testConstructors() {
assertEquals(Collections.<String, Object>emptyMap(), assertEquals(Collections.<String, Object>emptyMap(),
new BdfDictionary()); new BdfDictionary());
assertEquals(Collections.singletonMap("foo", NULL_VALUE), assertEquals(singletonMap("foo", NULL_VALUE),
new BdfDictionary(Collections.singletonMap("foo", NULL_VALUE))); new BdfDictionary(singletonMap("foo", NULL_VALUE)));
} }
@Test @Test
public void testFactoryMethod() { public void testFactoryMethod() {
assertEquals(Collections.<String, Object>emptyMap(), assertEquals(Collections.<String, Object>emptyMap(),
BdfDictionary.of()); BdfDictionary.of());
assertEquals(Collections.singletonMap("foo", NULL_VALUE), assertEquals(singletonMap("foo", NULL_VALUE),
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE))); BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)));
} }
@Test @Test
public void testIntegerPromotion() throws Exception { public void testLongPromotion() throws Exception {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("foo", (byte) 1); d.put("foo", (byte) 1);
d.put("bar", (short) 2); d.put("bar", (short) 2);
@@ -44,6 +48,33 @@ public class BdfDictionaryTest extends BrambleTestCase {
assertEquals(Long.valueOf(4), d.getLong("bam")); assertEquals(Long.valueOf(4), d.getLong("bam"));
} }
@Test
public void testIntPromotionAndDemotion() throws Exception {
BdfDictionary d = new BdfDictionary();
d.put("foo", (byte) 1);
d.put("bar", (short) 2);
d.put("baz", 3);
d.put("bam", 4L);
assertEquals(Integer.valueOf(1), d.getInt("foo"));
assertEquals(Integer.valueOf(2), d.getInt("bar"));
assertEquals(Integer.valueOf(3), d.getInt("baz"));
assertEquals(Integer.valueOf(4), d.getInt("bam"));
}
@Test(expected = FormatException.class)
public void testIntUnderflow() throws Exception {
BdfDictionary d =
BdfDictionary.of(new BdfEntry("foo", Integer.MIN_VALUE - 1L));
d.getInt("foo");
}
@Test(expected = FormatException.class)
public void testIntOverflow() throws Exception {
BdfDictionary d =
BdfDictionary.of(new BdfEntry("foo", Integer.MAX_VALUE + 1L));
d.getInt("foo");
}
@Test @Test
public void testFloatPromotion() throws Exception { public void testFloatPromotion() throws Exception {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
@@ -67,7 +98,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
} }
@Test @Test
public void testKeySetIteratorIsOrderedByKeys() throws Exception { public void testKeySetIteratorIsOrderedByKeys() {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("a", 1); d.put("a", 1);
d.put("d", 4); d.put("d", 4);
@@ -86,7 +117,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
} }
@Test @Test
public void testValuesIteratorIsOrderedByKeys() throws Exception { public void testValuesIteratorIsOrderedByKeys() {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("a", 1); d.put("a", 1);
d.put("d", 4); d.put("d", 4);
@@ -105,7 +136,7 @@ public class BdfDictionaryTest extends BrambleTestCase {
} }
@Test @Test
public void testEntrySetIteratorIsOrderedByKeys() throws Exception { public void testEntrySetIteratorIsOrderedByKeys() {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("a", 1); d.put("a", 1);
d.put("d", 4); d.put("d", 4);
@@ -130,4 +161,284 @@ public class BdfDictionaryTest extends BrambleTestCase {
assertEquals("d", e.getKey()); assertEquals("d", e.getKey());
assertEquals(4, e.getValue()); assertEquals(4, e.getValue());
} }
@Test(expected = FormatException.class)
public void testMissingValueForBooleanThrowsFormatException()
throws Exception {
new BdfDictionary().getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForLongThrowsFormatException()
throws Exception {
new BdfDictionary().getLong("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForIntThrowsFormatException() throws Exception {
new BdfDictionary().getInt("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForDoubleThrowsFormatException()
throws Exception {
new BdfDictionary().getDouble("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForStringThrowsFormatException()
throws Exception {
new BdfDictionary().getString("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForRawThrowsFormatException() throws Exception {
new BdfDictionary().getRaw("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForListThrowsFormatException()
throws Exception {
new BdfDictionary().getList("foo");
}
@Test(expected = FormatException.class)
public void testMissingValueForDictionaryThrowsFormatException()
throws Exception {
new BdfDictionary().getDictionary("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForLongThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getLong("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForIntThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getInt("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForDoubleThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getDouble("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForStringThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getString("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForRawThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getRaw("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForListThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getList("foo");
}
@Test(expected = FormatException.class)
public void testNullValueForDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", NULL_VALUE)).getDictionary("foo");
}
@Test
public void testOptionalMethodsReturnNullForMissingValue()
throws Exception {
testOptionalMethodsReturnNull(new BdfDictionary());
}
@Test
public void testOptionalMethodsReturnNullForNullValue() throws Exception {
BdfDictionary d = BdfDictionary.of(new BdfEntry("foo", NULL_VALUE));
testOptionalMethodsReturnNull(d);
}
private void testOptionalMethodsReturnNull(BdfDictionary d)
throws Exception {
assertNull(d.getOptionalBoolean("foo"));
assertNull(d.getOptionalLong("foo"));
assertNull(d.getOptionalInt("foo"));
assertNull(d.getOptionalDouble("foo"));
assertNull(d.getOptionalString("foo"));
assertNull(d.getOptionalRaw("foo"));
assertNull(d.getOptionalList("foo"));
assertNull(d.getOptionalDictionary("foo"));
}
@Test
public void testDefaultMethodsReturnDefaultForMissingValue()
throws Exception {
testDefaultMethodsReturnDefault(new BdfDictionary());
}
@Test
public void testDefaultMethodsReturnDefaultForNullValue() throws Exception {
BdfDictionary d = BdfDictionary.of(new BdfEntry("foo", NULL_VALUE));
testDefaultMethodsReturnDefault(d);
}
private void testDefaultMethodsReturnDefault(BdfDictionary d)
throws Exception {
assertEquals(TRUE, d.getBoolean("foo", TRUE));
assertEquals(Long.valueOf(123L), d.getLong("foo", 123L));
assertEquals(Integer.valueOf(123), d.getInt("foo", 123));
assertEquals(Double.valueOf(123D), d.getDouble("foo", 123D));
assertEquals("123", d.getString("foo", "123"));
byte[] defaultRaw = {1, 2, 3};
assertArrayEquals(defaultRaw, d.getRaw("foo", defaultRaw));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, d.getList("foo", defaultList));
BdfDictionary defaultDict = BdfDictionary.of(new BdfEntry("123", 123));
assertEquals(defaultDict, d.getDictionary("foo", defaultDict));
}
@Test(expected = FormatException.class)
public void testWrongTypeForBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getBoolean("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalBoolean("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultBooleanThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getBoolean("foo", true);
}
@Test(expected = FormatException.class)
public void testWrongTypeForLongThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getLong("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalLongThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getOptionalLong("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultLongThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getLong("foo", 1L);
}
@Test(expected = FormatException.class)
public void testWrongTypeForIntThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getInt("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalIntThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getOptionalInt("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultIntThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 1.23)).getInt("foo", 1);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDoubleThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDouble("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalDoubleThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalDouble("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDoubleThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDouble("foo", 1D);
}
@Test(expected = FormatException.class)
public void testWrongTypeForStringThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getString("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalStringThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalString("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultStringThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getString("foo", "");
}
@Test(expected = FormatException.class)
public void testWrongTypeForRawThrowsFormatException() throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getRaw("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalRawThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalRaw("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultRawThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getRaw("foo", new byte[0]);
}
@Test(expected = FormatException.class)
public void testWrongTypeForListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getList("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalList("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultListThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getList("foo",
new BdfList());
}
@Test(expected = FormatException.class)
public void testWrongTypeForDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDictionary("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getOptionalDictionary("foo");
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDictionaryThrowsFormatException()
throws Exception {
BdfDictionary.of(new BdfEntry("foo", 123)).getDictionary("foo",
new BdfDictionary());
}
} }

View File

@@ -5,31 +5,31 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test; import org.junit.Test;
import java.util.Arrays; import static java.lang.Boolean.TRUE;
import java.util.Collections; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class BdfListTest extends BrambleTestCase { public class BdfListTest extends BrambleTestCase {
@Test @Test
public void testConstructors() { public void testConstructors() {
assertEquals(Collections.emptyList(), new BdfList()); assertEquals(emptyList(), new BdfList());
assertEquals(Arrays.asList(1, 2, NULL_VALUE), assertEquals(asList(1, 2, NULL_VALUE),
new BdfList(Arrays.asList(1, 2, NULL_VALUE))); new BdfList(asList(1, 2, NULL_VALUE)));
} }
@Test @Test
public void testFactoryMethod() { public void testFactoryMethod() {
assertEquals(Collections.emptyList(), BdfList.of()); assertEquals(emptyList(), BdfList.of());
assertEquals(Arrays.asList(1, 2, NULL_VALUE), assertEquals(asList(1, 2, NULL_VALUE), BdfList.of(1, 2, NULL_VALUE));
BdfList.of(1, 2, NULL_VALUE));
} }
@Test @Test
public void testIntegerPromotion() throws Exception { public void testLongPromotion() throws Exception {
BdfList list = new BdfList(); BdfList list = new BdfList();
list.add((byte) 1); list.add((byte) 1);
list.add((short) 2); list.add((short) 2);
@@ -41,6 +41,31 @@ public class BdfListTest extends BrambleTestCase {
assertEquals(Long.valueOf(4), list.getLong(3)); assertEquals(Long.valueOf(4), list.getLong(3));
} }
@Test
public void testIntPromotionAndDemotion() throws Exception {
BdfList list = new BdfList();
list.add((byte) 1);
list.add((short) 2);
list.add(3);
list.add(4L);
assertEquals(Integer.valueOf(1), list.getInt(0));
assertEquals(Integer.valueOf(2), list.getInt(1));
assertEquals(Integer.valueOf(3), list.getInt(2));
assertEquals(Integer.valueOf(4), list.getInt(3));
}
@Test(expected = FormatException.class)
public void testIntUnderflow() throws Exception {
BdfList list = BdfList.of(Integer.MIN_VALUE - 1L);
list.getInt(0);
}
@Test(expected = FormatException.class)
public void testIntOverflow() throws Exception {
BdfList list = BdfList.of(Integer.MAX_VALUE + 1L);
list.getInt(0);
}
@Test @Test
public void testFloatPromotion() throws Exception { public void testFloatPromotion() throws Exception {
BdfList list = new BdfList(); BdfList list = new BdfList();
@@ -63,61 +88,6 @@ public class BdfListTest extends BrambleTestCase {
assertArrayEquals(new byte[123], second); assertArrayEquals(new byte[123], second);
} }
@Test
@SuppressWarnings("ConstantConditions")
public void testIndexOutOfBoundsReturnsDefaultValue() throws Exception {
BdfList list = BdfList.of(1, 2, 3);
boolean defaultBoolean = true;
assertEquals(defaultBoolean, list.getBoolean(-1, defaultBoolean));
assertEquals(defaultBoolean, list.getBoolean(3, defaultBoolean));
Long defaultLong = 123L;
assertEquals(defaultLong, list.getLong(-1, defaultLong));
assertEquals(defaultLong, list.getLong(3, defaultLong));
Double defaultDouble = 1.23;
assertEquals(defaultDouble, list.getDouble(-1, defaultDouble));
assertEquals(defaultDouble, list.getDouble(3, defaultDouble));
String defaultString = "123";
assertEquals(defaultString, list.getString(-1, defaultString));
assertEquals(defaultString, list.getString(3, defaultString));
byte[] defaultBytes = new byte[] {1, 2, 3};
assertArrayEquals(defaultBytes, list.getRaw(-1, defaultBytes));
assertArrayEquals(defaultBytes, list.getRaw(3, defaultBytes));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(-1, defaultList));
assertEquals(defaultList, list.getList(3, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(
new BdfEntry("1", 1),
new BdfEntry("2", 2),
new BdfEntry("3", 3)
);
assertEquals(defaultDict, list.getDictionary(-1, defaultDict));
assertEquals(defaultDict, list.getDictionary(3, defaultDict));
}
@Test
@SuppressWarnings("ConstantConditions")
public void testWrongTypeReturnsDefaultValue() throws Exception {
BdfList list = BdfList.of(1, 2, 3, true);
boolean defaultBoolean = true;
assertEquals(defaultBoolean, list.getBoolean(0, defaultBoolean));
Long defaultLong = 123L;
assertEquals(defaultLong, list.getLong(3, defaultLong));
Double defaultDouble = 1.23;
assertEquals(defaultDouble, list.getDouble(0, defaultDouble));
String defaultString = "123";
assertEquals(defaultString, list.getString(0, defaultString));
byte[] defaultBytes = new byte[] {1, 2, 3};
assertArrayEquals(defaultBytes, list.getRaw(0, defaultBytes));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(0, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(
new BdfEntry("1", 1),
new BdfEntry("2", 2),
new BdfEntry("3", 3)
);
assertEquals(defaultDict, list.getDictionary(0, defaultDict));
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testNegativeIndexForBooleanThrowsFormatException() public void testNegativeIndexForBooleanThrowsFormatException()
throws Exception { throws Exception {
@@ -130,6 +100,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalBoolean(-1); new BdfList().getOptionalBoolean(-1);
} }
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultBooleanThrowsFormatException()
throws Exception {
new BdfList().getBoolean(-1, true);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testNegativeIndexForLongThrowsFormatException() public void testNegativeIndexForLongThrowsFormatException()
throws Exception { throws Exception {
@@ -142,6 +118,30 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalLong(-1); new BdfList().getOptionalLong(-1);
} }
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultLongThrowsFormatException()
throws Exception {
new BdfList().getLong(-1, 1L);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForIntThrowsFormatException()
throws Exception {
new BdfList().getInt(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForOptionalIntThrowsFormatException()
throws Exception {
new BdfList().getOptionalInt(-1);
}
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultIntThrowsFormatException()
throws Exception {
new BdfList().getInt(-1, 1);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testNegativeIndexForDoubleThrowsFormatException() public void testNegativeIndexForDoubleThrowsFormatException()
throws Exception { throws Exception {
@@ -154,6 +154,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDouble(-1); new BdfList().getOptionalDouble(-1);
} }
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultDoubleThrowsFormatException()
throws Exception {
new BdfList().getDouble(-1, 1D);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testNegativeIndexForStringThrowsFormatException() public void testNegativeIndexForStringThrowsFormatException()
throws Exception { throws Exception {
@@ -166,6 +172,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalString(-1); new BdfList().getOptionalString(-1);
} }
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultStringThrowsFormatException()
throws Exception {
new BdfList().getString(-1, "");
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testNegativeIndexForRawThrowsFormatException() public void testNegativeIndexForRawThrowsFormatException()
throws Exception { throws Exception {
@@ -178,6 +190,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalRaw(-1); new BdfList().getOptionalRaw(-1);
} }
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultRawThrowsFormatException()
throws Exception {
new BdfList().getRaw(-1, new byte[0]);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testNegativeIndexForListThrowsFormatException() public void testNegativeIndexForListThrowsFormatException()
throws Exception { throws Exception {
@@ -190,6 +208,11 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalList(-1); new BdfList().getOptionalList(-1);
} }
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultListThrowsFormatException()
throws Exception {
new BdfList().getList(-1, new BdfList());
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testNegativeIndexForDictionaryThrowsFormatException() public void testNegativeIndexForDictionaryThrowsFormatException()
@@ -203,6 +226,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDictionary(-1); new BdfList().getOptionalDictionary(-1);
} }
@Test(expected = FormatException.class)
public void testNegativeIndexForDefaultDictionaryThrowsFormatException()
throws Exception {
new BdfList().getDictionary(-1, new BdfDictionary());
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testTooLargeIndexForBooleanThrowsFormatException() public void testTooLargeIndexForBooleanThrowsFormatException()
throws Exception { throws Exception {
@@ -215,6 +244,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalBoolean(0); new BdfList().getOptionalBoolean(0);
} }
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultBooleanThrowsFormatException()
throws Exception {
new BdfList().getBoolean(0, true);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testTooLargeIndexForLongThrowsFormatException() public void testTooLargeIndexForLongThrowsFormatException()
throws Exception { throws Exception {
@@ -227,6 +262,30 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalLong(0); new BdfList().getOptionalLong(0);
} }
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultLongThrowsFormatException()
throws Exception {
new BdfList().getLong(0, 1L);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForIntThrowsFormatException()
throws Exception {
new BdfList().getInt(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForOptionalIntThrowsFormatException()
throws Exception {
new BdfList().getOptionalInt(0);
}
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultIntThrowsFormatException()
throws Exception {
new BdfList().getInt(0, 1);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testTooLargeIndexForDoubleThrowsFormatException() public void testTooLargeIndexForDoubleThrowsFormatException()
throws Exception { throws Exception {
@@ -239,6 +298,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalDouble(0); new BdfList().getOptionalDouble(0);
} }
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultDoubleThrowsFormatException()
throws Exception {
new BdfList().getDouble(0, 1D);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testTooLargeIndexForStringThrowsFormatException() public void testTooLargeIndexForStringThrowsFormatException()
throws Exception { throws Exception {
@@ -251,6 +316,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalString(0); new BdfList().getOptionalString(0);
} }
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultStringThrowsFormatException()
throws Exception {
new BdfList().getString(0, "");
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testTooLargeIndexForRawThrowsFormatException() public void testTooLargeIndexForRawThrowsFormatException()
throws Exception { throws Exception {
@@ -263,6 +334,12 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalRaw(0); new BdfList().getOptionalRaw(0);
} }
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultRawThrowsFormatException()
throws Exception {
new BdfList().getRaw(0, new byte[0]);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testTooLargeIndexForListThrowsFormatException() public void testTooLargeIndexForListThrowsFormatException()
throws Exception { throws Exception {
@@ -275,6 +352,11 @@ public class BdfListTest extends BrambleTestCase {
new BdfList().getOptionalList(0); new BdfList().getOptionalList(0);
} }
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultListThrowsFormatException()
throws Exception {
new BdfList().getList(0, new BdfList());
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testTooLargeIndexForDictionaryThrowsFormatException() public void testTooLargeIndexForDictionaryThrowsFormatException()
@@ -287,6 +369,13 @@ public class BdfListTest extends BrambleTestCase {
throws Exception { throws Exception {
new BdfList().getOptionalDictionary(0); new BdfList().getOptionalDictionary(0);
} }
@Test(expected = FormatException.class)
public void testTooLargeIndexForDefaultDictionaryThrowsFormatException()
throws Exception {
new BdfList().getDictionary(0, new BdfDictionary());
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testWrongTypeForBooleanThrowsFormatException() public void testWrongTypeForBooleanThrowsFormatException()
throws Exception { throws Exception {
@@ -299,6 +388,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalBoolean(0); BdfList.of(123).getOptionalBoolean(0);
} }
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultBooleanThrowsFormatException()
throws Exception {
BdfList.of(123).getBoolean(0, true);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testWrongTypeForLongThrowsFormatException() throws Exception { public void testWrongTypeForLongThrowsFormatException() throws Exception {
BdfList.of(1.23).getLong(0); BdfList.of(1.23).getLong(0);
@@ -310,6 +405,29 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(1.23).getOptionalLong(0); BdfList.of(1.23).getOptionalLong(0);
} }
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultLongThrowsFormatException()
throws Exception {
BdfList.of(1.23).getLong(0, 1L);
}
@Test(expected = FormatException.class)
public void testWrongTypeForIntThrowsFormatException() throws Exception {
BdfList.of(1.23).getInt(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForOptionalIntThrowsFormatException()
throws Exception {
BdfList.of(1.23).getOptionalInt(0);
}
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultIntThrowsFormatException()
throws Exception {
BdfList.of(1.23).getInt(0, 1);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testWrongTypeForDoubleThrowsFormatException() throws Exception { public void testWrongTypeForDoubleThrowsFormatException() throws Exception {
BdfList.of(123).getDouble(0); BdfList.of(123).getDouble(0);
@@ -321,6 +439,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalDouble(0); BdfList.of(123).getOptionalDouble(0);
} }
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDoubleThrowsFormatException()
throws Exception {
BdfList.of(123).getDouble(0, 1D);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testWrongTypeForStringThrowsFormatException() throws Exception { public void testWrongTypeForStringThrowsFormatException() throws Exception {
BdfList.of(123).getString(0); BdfList.of(123).getString(0);
@@ -332,6 +456,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalString(0); BdfList.of(123).getOptionalString(0);
} }
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultStringThrowsFormatException()
throws Exception {
BdfList.of(123).getString(0, "");
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testWrongTypeForRawThrowsFormatException() throws Exception { public void testWrongTypeForRawThrowsFormatException() throws Exception {
BdfList.of(123).getRaw(0); BdfList.of(123).getRaw(0);
@@ -343,6 +473,12 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalRaw(0); BdfList.of(123).getOptionalRaw(0);
} }
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultRawThrowsFormatException()
throws Exception {
BdfList.of(123).getRaw(0, new byte[0]);
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testWrongTypeForListThrowsFormatException() throws Exception { public void testWrongTypeForListThrowsFormatException() throws Exception {
BdfList.of(123).getList(0); BdfList.of(123).getList(0);
@@ -354,6 +490,11 @@ public class BdfListTest extends BrambleTestCase {
BdfList.of(123).getOptionalList(0); BdfList.of(123).getOptionalList(0);
} }
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultListThrowsFormatException()
throws Exception {
BdfList.of(123).getList(0, new BdfList());
}
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testWrongTypeForDictionaryThrowsFormatException() public void testWrongTypeForDictionaryThrowsFormatException()
@@ -366,4 +507,81 @@ public class BdfListTest extends BrambleTestCase {
throws Exception { throws Exception {
BdfList.of(123).getOptionalDictionary(0); BdfList.of(123).getOptionalDictionary(0);
} }
@Test(expected = FormatException.class)
public void testWrongTypeForDefaultDictionaryThrowsFormatException()
throws Exception {
BdfList.of(123).getDictionary(0, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testNullValueForBooleanThrowsFormatException()
throws Exception {
BdfList.of(NULL_VALUE).getBoolean(0);
}
@Test(expected = FormatException.class)
public void testNullValueForLongThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getLong(0);
}
@Test(expected = FormatException.class)
public void testNullValueForIntThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getInt(0);
}
@Test(expected = FormatException.class)
public void testNullValueForDoubleThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getDouble(0);
}
@Test(expected = FormatException.class)
public void testNullValueForStringThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getString(0);
}
@Test(expected = FormatException.class)
public void testNullValueForRawThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getRaw(0);
}
@Test(expected = FormatException.class)
public void testNullValueForListThrowsFormatException() throws Exception {
BdfList.of(NULL_VALUE).getList(0);
}
@Test(expected = FormatException.class)
public void testNullValueForDictionaryThrowsFormatException()
throws Exception {
BdfList.of(NULL_VALUE).getDictionary(0);
}
@Test
public void testOptionalMethodsReturnNullForNullValue() throws Exception {
BdfList list = BdfList.of(NULL_VALUE);
assertNull(list.getOptionalBoolean(0));
assertNull(list.getOptionalLong(0));
assertNull(list.getOptionalInt(0));
assertNull(list.getOptionalDouble(0));
assertNull(list.getOptionalString(0));
assertNull(list.getOptionalRaw(0));
assertNull(list.getOptionalList(0));
assertNull(list.getOptionalDictionary(0));
}
@Test
public void testDefaultMethodsReturnDefaultForNullValue() throws Exception {
BdfList list = BdfList.of(NULL_VALUE);
assertEquals(TRUE, list.getBoolean(0, TRUE));
assertEquals(Long.valueOf(123L), list.getLong(0, 123L));
assertEquals(Integer.valueOf(123), list.getInt(0, 123));
assertEquals(Double.valueOf(123D), list.getDouble(0, 123D));
assertEquals("123", list.getString(0, "123"));
byte[] defaultRaw = {1, 2, 3};
assertArrayEquals(defaultRaw, list.getRaw(0, defaultRaw));
BdfList defaultList = BdfList.of(1, 2, 3);
assertEquals(defaultList, list.getList(0, defaultList));
BdfDictionary defaultDict = BdfDictionary.of(new BdfEntry("123", 123));
assertEquals(defaultDict, list.getDictionary(0, defaultDict));
}
} }

View File

@@ -4,7 +4,7 @@ dependencyVerification {
'com.fasterxml.jackson.core:jackson-annotations:2.13.4:jackson-annotations-2.13.4.jar:ac5b27a634942391ca113850ee7db01df1499a240174021263501c05fc653b44', 'com.fasterxml.jackson.core:jackson-annotations:2.13.4:jackson-annotations-2.13.4.jar:ac5b27a634942391ca113850ee7db01df1499a240174021263501c05fc653b44',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79', 'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger:2.43.2:dagger-2.43.2.jar:c89681f7cbbf8c527bf4ac2748515d617fdb54a1d425c08d914fdc28192b5fe4', 'com.google.dagger:dagger:2.45:dagger-2.45.jar:f011cae7d2c0fb7ea17c34e05bc10e768b1081a5892ad019cf1fdb0e125c49c1',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11', 'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
@@ -12,8 +12,8 @@ dependencyVerification {
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011', 'org.briarproject:null-safety:0.1:null-safety-0.1.jar:161760de5e838cb982bafa973df820675d4397098e9a91637a36a306d43ba011',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac', 'org.codehaus.mojo:animal-sniffer-ant-tasks:1.22:animal-sniffer-ant-tasks-1.22.jar:3f6afeb3e09301d2d7179ed1db21e3ad8846c1e38415ad832a395138ae3f4218',
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249', 'org.codehaus.mojo:animal-sniffer:1.22:animal-sniffer-1.22.jar:f18c11a25bdd8b520b9c6a28cbb6f33007c812ab0051b6be3f0778e660aa501c',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050', 'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
@@ -24,6 +24,6 @@ dependencyVerification {
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be', 'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984', 'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de', 'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2', 'org.ow2.asm:asm:9.3:asm-9.3.jar:1263369b59e29c943918de11d6d6152e2ec6085ce63e5710516f8c67d368e4bc',
] ]
} }

View File

@@ -11,11 +11,12 @@ apply from: '../dagger.gradle'
dependencies { dependencies {
api project(':bramble-api') api project(':bramble-api')
api 'org.briarproject:jtorctl:0.5' api "org.briarproject:onionwrapper-core:$onionwrapper_version"
implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version" implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version"
//noinspection GradleDependency //noinspection GradleDependency
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6 implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation "io.github.willena:sqlite-jdbc:$sqlite_jdbc_crypt_version"
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
@@ -28,7 +29,7 @@ dependencies {
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6 testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'net.jodah:concurrentunit:0.4.2' testImplementation 'net.jodah:concurrentunit:0.4.6'
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"

View File

@@ -155,7 +155,13 @@ class ClientHelperImpl implements ClientHelper {
@Override @Override
public BdfList getMessageAsList(Transaction txn, MessageId m) public BdfList getMessageAsList(Transaction txn, MessageId m)
throws DbException, FormatException { throws DbException, FormatException {
return toList(db.getMessage(txn, m).getBody()); return getMessageAsList(txn, m, true);
}
@Override
public BdfList getMessageAsList(Transaction txn, MessageId m,
boolean canonical) throws DbException, FormatException {
return toList(db.getMessage(txn, m), canonical);
} }
@Override @Override
@@ -313,8 +319,13 @@ class ClientHelperImpl implements ClientHelper {
@Override @Override
public BdfList toList(byte[] b, int off, int len) throws FormatException { public BdfList toList(byte[] b, int off, int len) throws FormatException {
return toList(b, off, len, true);
}
private BdfList toList(byte[] b, int off, int len, boolean canonical)
throws FormatException {
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len); ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
BdfReader reader = bdfReaderFactory.createReader(in); BdfReader reader = bdfReaderFactory.createReader(in, canonical);
try { try {
BdfList list = reader.readList(); BdfList list = reader.readList();
if (!reader.eof()) throw new FormatException(); if (!reader.eof()) throw new FormatException();
@@ -328,7 +339,7 @@ class ClientHelperImpl implements ClientHelper {
@Override @Override
public BdfList toList(byte[] b) throws FormatException { public BdfList toList(byte[] b) throws FormatException {
return toList(b, 0, b.length); return toList(b, 0, b.length, true);
} }
@Override @Override
@@ -336,6 +347,12 @@ class ClientHelperImpl implements ClientHelper {
return toList(m.getBody()); return toList(m.getBody());
} }
@Override
public BdfList toList(Message m, boolean canonical) throws FormatException {
byte[] b = m.getBody();
return toList(b, 0, b.length, canonical);
}
@Override @Override
public BdfList toList(Author a) { public BdfList toList(Author a) {
return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey()); return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey());
@@ -361,7 +378,7 @@ class ClientHelperImpl implements ClientHelper {
public Author parseAndValidateAuthor(BdfList author) public Author parseAndValidateAuthor(BdfList author)
throws FormatException { throws FormatException {
checkSize(author, 3); checkSize(author, 3);
int formatVersion = author.getLong(0).intValue(); int formatVersion = author.getInt(0);
if (formatVersion != FORMAT_VERSION) throw new FormatException(); if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = author.getString(1); String name = author.getString(1);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH); checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
@@ -472,8 +489,7 @@ class ClientHelperImpl implements ClientHelper {
if (element.size() != 2) { if (element.size() != 2) {
throw new FormatException(); throw new FormatException();
} }
list.add(new MailboxVersion(element.getLong(0).intValue(), list.add(new MailboxVersion(element.getInt(0), element.getInt(1)));
element.getLong(1).intValue()));
} }
// Sort the list of versions for easier comparison // Sort the list of versions for easier comparison
sort(list); sort(list);
@@ -486,7 +502,7 @@ class ClientHelperImpl implements ClientHelper {
try { try {
BdfDictionary meta = BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId); getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue()); return new ContactId(meta.getInt(GROUP_KEY_CONTACT_ID));
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); // Invalid group metadata throw new DbException(e); // Invalid group metadata
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.contact; package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactExchangeManager;
@@ -24,6 +23,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory; import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -61,12 +61,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
getLogger(ContactExchangeManagerImpl.class.getName()); getLogger(ContactExchangeManagerImpl.class.getName());
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static final RecordPredicate ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -5,14 +5,31 @@ import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
interface HandshakeConstants { interface HandshakeConstants {
/** /**
* The current version of the handshake protocol. * The current major version of the handshake protocol.
*/ */
byte PROTOCOL_VERSION = 0; byte PROTOCOL_MAJOR_VERSION = 0;
/** /**
* Label for deriving the master key. * The current minor version of the handshake protocol.
*/ */
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY"; byte PROTOCOL_MINOR_VERSION = 1;
/**
* Label for deriving the master key when using the deprecated v0.0 key
* derivation method.
* <p>
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
*/
@Deprecated
String MASTER_KEY_LABEL_0_0 =
"org.briarproject.bramble.handshake/MASTER_KEY";
/**
* Label for deriving the master key when using the v0.1 key derivation
* method.
*/
String MASTER_KEY_LABEL_0_1 =
"org.briarproject.bramble.handshake/MASTER_KEY_0_1";
/** /**
* Label for deriving Alice's proof of ownership from the master key. * Label for deriving Alice's proof of ownership from the master key.

View File

@@ -13,11 +13,26 @@ interface HandshakeCrypto {
KeyPair generateEphemeralKeyPair(); KeyPair generateEphemeralKeyPair();
/** /**
* Derives the master key from the given static and ephemeral keys. * Derives the master key from the given static and ephemeral keys using
* the deprecated v0.0 key derivation method.
* <p>
* TODO: Remove this after a reasonable migration period (added 2023-03-10).
* *
* @param alice Whether the local peer is Alice * @param alice Whether the local peer is Alice
*/ */
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey, @Deprecated
SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice)
throws GeneralSecurityException;
/**
* Derives the master key from the given static and ephemeral keys using
* the v0.1 key derivation method.
*
* @param alice Whether the local peer is Alice
*/
SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair, PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) KeyPair ourEphemeralKeyPair, boolean alice)
throws GeneralSecurityException; throws GeneralSecurityException;

View File

@@ -13,7 +13,8 @@ import javax.inject.Inject;
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_0;
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL_0_1;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -32,7 +33,8 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
} }
@Override @Override
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey, @Deprecated
public SecretKey deriveMasterKey_0_0(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair, PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) throws KeyPair ourEphemeralKeyPair, boolean alice) throws
GeneralSecurityException { GeneralSecurityException {
@@ -46,9 +48,29 @@ class HandshakeCryptoImpl implements HandshakeCrypto {
alice ? ourEphemeral : theirEphemeral, alice ? ourEphemeral : theirEphemeral,
alice ? theirEphemeral : ourEphemeral alice ? theirEphemeral : ourEphemeral
}; };
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey, return crypto.deriveSharedSecretBadly(MASTER_KEY_LABEL_0_0,
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair, theirStaticPublicKey, theirEphemeralPublicKey,
alice, inputs); ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
}
@Override
public SecretKey deriveMasterKey_0_1(PublicKey theirStaticPublicKey,
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
KeyPair ourEphemeralKeyPair, boolean alice) throws
GeneralSecurityException {
byte[] theirStatic = theirStaticPublicKey.getEncoded();
byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded();
byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded();
byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded();
byte[][] inputs = {
alice ? ourStatic : theirStatic,
alice ? theirStatic : ourStatic,
alice ? ourEphemeral : theirEphemeral,
alice ? theirEphemeral : ourEphemeral
};
return crypto.deriveSharedSecret(MASTER_KEY_LABEL_0_1,
theirStaticPublicKey, theirEphemeralPublicKey,
ourStaticKeyPair, ourEphemeralKeyPair, alice, inputs);
} }
@Override @Override

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager; import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
@@ -12,12 +11,12 @@ import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory; import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -28,15 +27,20 @@ import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES; import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY; import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP; import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@Immutable @Immutable
@@ -44,12 +48,14 @@ import static org.briarproject.bramble.util.ValidationUtils.checkLength;
class HandshakeManagerImpl implements HandshakeManager { class HandshakeManagerImpl implements HandshakeManager {
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());
private static boolean isKnownRecordType(byte type) { private static boolean isKnownRecordType(byte type) {
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP; return type == RECORD_TYPE_EPHEMERAL_PUBLIC_KEY ||
type == RECORD_TYPE_PROOF_OF_OWNERSHIP ||
type == RECORD_TYPE_MINOR_VERSION;
} }
private final TransactionManager db; private final TransactionManager db;
@@ -61,7 +67,7 @@ class HandshakeManagerImpl implements HandshakeManager {
private final RecordWriterFactory recordWriterFactory; private final RecordWriterFactory recordWriterFactory;
@Inject @Inject
HandshakeManagerImpl(DatabaseComponent db, HandshakeManagerImpl(TransactionManager db,
IdentityManager identityManager, IdentityManager identityManager,
ContactManager contactManager, ContactManager contactManager,
TransportCrypto transportCrypto, TransportCrypto transportCrypto,
@@ -95,19 +101,31 @@ class HandshakeManagerImpl implements HandshakeManager {
.createRecordWriter(out.getOutputStream()); .createRecordWriter(out.getOutputStream());
KeyPair ourEphemeralKeyPair = KeyPair ourEphemeralKeyPair =
handshakeCrypto.generateEphemeralKeyPair(); handshakeCrypto.generateEphemeralKeyPair();
PublicKey theirEphemeralPublicKey; Pair<Byte, PublicKey> theirMinorVersionAndKey;
if (alice) { if (alice) {
sendMinorVersion(recordWriter);
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
theirEphemeralPublicKey = receivePublicKey(recordReader); theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
} else { } else {
theirEphemeralPublicKey = receivePublicKey(recordReader); theirMinorVersionAndKey = receiveMinorVersionAndKey(recordReader);
sendMinorVersion(recordWriter);
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
} }
byte theirMinorVersion = theirMinorVersionAndKey.getFirst();
PublicKey theirEphemeralPublicKey = theirMinorVersionAndKey.getSecond();
SecretKey masterKey; SecretKey masterKey;
try { try {
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey, if (theirMinorVersion > 0) {
theirEphemeralPublicKey, ourStaticKeyPair, masterKey = handshakeCrypto.deriveMasterKey_0_1(
ourEphemeralKeyPair, alice); theirStaticPublicKey, theirEphemeralPublicKey,
ourStaticKeyPair, ourEphemeralKeyPair, alice);
} else {
// TODO: Remove this branch after a reasonable migration
// period (added 2023-03-10).
masterKey = handshakeCrypto.deriveMasterKey_0_0(
theirStaticPublicKey, theirEphemeralPublicKey,
ourStaticKeyPair, ourEphemeralKeyPair, alice);
}
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new FormatException(); throw new FormatException();
} }
@@ -128,34 +146,91 @@ class HandshakeManagerImpl implements HandshakeManager {
} }
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException { private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY, w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
k.getEncoded())); RECORD_TYPE_EPHEMERAL_PUBLIC_KEY, k.getEncoded()));
w.flush(); w.flush();
} }
private PublicKey receivePublicKey(RecordReader r) throws IOException { /**
byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload(); * Receives the remote peer's protocol minor version and ephemeral public
* key.
* <p>
* In version 0.1 of the protocol, each peer sends a minor version record
* followed by an ephemeral public key record.
* <p>
* In version 0.0 of the protocol, each peer sends an ephemeral public key
* record without a preceding minor version record.
* <p>
* Therefore the remote peer's minor version must be non-zero if a minor
* version record is received, and is assumed to be zero if no minor
* version record is received.
*/
private Pair<Byte, PublicKey> receiveMinorVersionAndKey(RecordReader r)
throws IOException {
byte theirMinorVersion;
PublicKey theirEphemeralPublicKey;
// The first record can be either a minor version record or an
// ephemeral public key record
Record first = readRecord(r, asList(RECORD_TYPE_MINOR_VERSION,
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
if (first.getRecordType() == RECORD_TYPE_MINOR_VERSION) {
// The payload must be a single byte giving the remote peer's
// protocol minor version, which must be non-zero
byte[] payload = first.getPayload();
checkLength(payload, 1);
theirMinorVersion = payload[0];
if (theirMinorVersion == 0) throw new FormatException();
// The second record must be an ephemeral public key record
Record second = readRecord(r,
singletonList(RECORD_TYPE_EPHEMERAL_PUBLIC_KEY));
theirEphemeralPublicKey = parsePublicKey(second);
} else {
// The remote peer did not send a minor version record, so the
// remote peer's protocol minor version is assumed to be zero
// TODO: Remove this branch after a reasonable migration period
// (added 2023-03-10).
theirMinorVersion = 0;
theirEphemeralPublicKey = parsePublicKey(first);
}
return new Pair<>(theirMinorVersion, theirEphemeralPublicKey);
}
private PublicKey parsePublicKey(Record rec) throws IOException {
if (rec.getRecordType() != RECORD_TYPE_EPHEMERAL_PUBLIC_KEY) {
throw new AssertionError();
}
byte[] key = rec.getPayload();
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES); checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
return new AgreementPublicKey(key); return new AgreementPublicKey(key);
} }
private void sendProof(RecordWriter w, byte[] proof) throws IOException { private void sendProof(RecordWriter w, byte[] proof) throws IOException {
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof)); w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_PROOF_OF_OWNERSHIP, proof));
w.flush(); w.flush();
} }
private byte[] receiveProof(RecordReader r) throws IOException { private byte[] receiveProof(RecordReader r) throws IOException {
byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload(); Record rec = readRecord(r,
singletonList(RECORD_TYPE_PROOF_OF_OWNERSHIP));
byte[] proof = rec.getPayload();
checkLength(proof, PROOF_BYTES, PROOF_BYTES); checkLength(proof, PROOF_BYTES, PROOF_BYTES);
return proof; return proof;
} }
private Record readRecord(RecordReader r, byte expectedType) private void sendMinorVersion(RecordWriter w) throws IOException {
w.writeRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_MINOR_VERSION,
new byte[] {PROTOCOL_MINOR_VERSION}));
w.flush();
}
private Record readRecord(RecordReader r, List<Byte> expectedTypes)
throws IOException { throws IOException {
// Accept records with current protocol version, expected type only // Accept records with current protocol version, expected types only
Predicate<Record> accept = rec -> RecordPredicate accept = rec ->
rec.getProtocolVersion() == PROTOCOL_VERSION && rec.getProtocolVersion() == PROTOCOL_MAJOR_VERSION &&
rec.getRecordType() == expectedType; expectedTypes.contains(rec.getRecordType());
Record rec = r.readRecord(accept, IGNORE); Record rec = r.readRecord(accept, IGNORE);
if (rec == null) throw new EOFException(); if (rec == null) throw new EOFException();
return rec; return rec;

View File

@@ -5,7 +5,9 @@ package org.briarproject.bramble.contact;
*/ */
interface HandshakeRecordTypes { interface HandshakeRecordTypes {
byte EPHEMERAL_PUBLIC_KEY = 0; byte RECORD_TYPE_EPHEMERAL_PUBLIC_KEY = 0;
byte PROOF_OF_OWNERSHIP = 1; byte RECORD_TYPE_PROOF_OF_OWNERSHIP = 1;
byte RECORD_TYPE_MINOR_VERSION = 2;
} }

View File

@@ -34,6 +34,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.util.Locale;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -222,7 +223,8 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public SecretKey deriveSharedSecret(String label, @Deprecated
public SecretKey deriveSharedSecretBadly(String label,
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey, PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair, KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
boolean alice, byte[]... inputs) throws GeneralSecurityException { boolean alice, byte[]... inputs) throws GeneralSecurityException {
@@ -250,6 +252,35 @@ class CryptoComponentImpl implements CryptoComponent {
return new SecretKey(hash); return new SecretKey(hash);
} }
@Override
public SecretKey deriveSharedSecret(String label,
PublicKey theirStaticPublicKey, PublicKey theirEphemeralPublicKey,
KeyPair ourStaticKeyPair, KeyPair ourEphemeralKeyPair,
boolean alice, byte[]... inputs) throws GeneralSecurityException {
PrivateKey ourStaticPrivateKey = ourStaticKeyPair.getPrivate();
PrivateKey ourEphemeralPrivateKey = ourEphemeralKeyPair.getPrivate();
byte[][] hashInputs = new byte[inputs.length + 3][];
// Alice ephemeral/Bob ephemeral
hashInputs[0] = performRawKeyAgreement(ourEphemeralPrivateKey,
theirEphemeralPublicKey);
// Alice static/Bob ephemeral, Bob static/Alice ephemeral
if (alice) {
hashInputs[1] = performRawKeyAgreement(ourStaticPrivateKey,
theirEphemeralPublicKey);
hashInputs[2] = performRawKeyAgreement(ourEphemeralPrivateKey,
theirStaticPublicKey);
} else {
hashInputs[1] = performRawKeyAgreement(ourEphemeralPrivateKey,
theirStaticPublicKey);
hashInputs[2] = performRawKeyAgreement(ourStaticPrivateKey,
theirEphemeralPublicKey);
}
arraycopy(inputs, 0, hashInputs, 3, inputs.length);
byte[] hash = hash(label, hashInputs);
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
return new SecretKey(hash);
}
@Override @Override
public byte[] sign(String label, byte[] toSign, PrivateKey privateKey) public byte[] sign(String label, byte[] toSign, PrivateKey privateKey)
throws GeneralSecurityException { throws GeneralSecurityException {
@@ -470,7 +501,7 @@ class CryptoComponentImpl implements CryptoComponent {
arraycopy(publicKey, 0, address, 0, publicKey.length); arraycopy(publicKey, 0, address, 0, publicKey.length);
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES); arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION; address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
return Base32.encode(address).toLowerCase(); return Base32.encode(address).toLowerCase(Locale.US);
} }
} }

View File

@@ -18,12 +18,18 @@ class BdfReaderFactoryImpl implements BdfReaderFactory {
@Override @Override
public BdfReader createReader(InputStream in) { public BdfReader createReader(InputStream in) {
return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT, return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE); DEFAULT_MAX_BUFFER_SIZE, true);
}
@Override
public BdfReader createReader(InputStream in, boolean canonical) {
return new BdfReaderImpl(in, DEFAULT_NESTED_LIMIT,
DEFAULT_MAX_BUFFER_SIZE, canonical);
} }
@Override @Override
public BdfReader createReader(InputStream in, int nestedLimit, public BdfReader createReader(InputStream in, int nestedLimit,
int maxBufferSize) { int maxBufferSize, boolean canonical) {
return new BdfReaderImpl(in, nestedLimit, maxBufferSize); return new BdfReaderImpl(in, nestedLimit, maxBufferSize, canonical);
} }
} }

View File

@@ -33,21 +33,24 @@ import static org.briarproject.bramble.util.StringUtils.fromUtf8;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class BdfReaderImpl implements BdfReader { final class BdfReaderImpl implements BdfReader {
private static final byte[] EMPTY_BUFFER = new byte[0]; private static final byte[] EMPTY_BUFFER = new byte[0];
private final InputStream in; private final InputStream in;
private final int nestedLimit, maxBufferSize; private final int nestedLimit, maxBufferSize;
private final boolean canonical;
private boolean hasLookahead = false, eof = false; private boolean hasLookahead = false, eof = false;
private byte next; private byte next;
private byte[] buf = new byte[8]; private byte[] buf = new byte[8];
BdfReaderImpl(InputStream in, int nestedLimit, int maxBufferSize) { BdfReaderImpl(InputStream in, int nestedLimit, int maxBufferSize,
boolean canonical) {
this.in = in; this.in = in;
this.nestedLimit = nestedLimit; this.nestedLimit = nestedLimit;
this.maxBufferSize = maxBufferSize; this.maxBufferSize = maxBufferSize;
this.canonical = canonical;
} }
private void readLookahead() throws IOException { private void readLookahead() throws IOException {
@@ -188,13 +191,22 @@ class BdfReaderImpl implements BdfReader {
private short readInt16() throws IOException { private short readInt16() throws IOException {
readIntoBuffer(2); readIntoBuffer(2);
return (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF)); short value = (short) (((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF));
if (canonical && value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
// Value could have been encoded as an INT_8
throw new FormatException();
}
return value;
} }
private int readInt32() throws IOException { private int readInt32() throws IOException {
readIntoBuffer(4); readIntoBuffer(4);
int value = 0; int value = 0;
for (int i = 0; i < 4; i++) value |= (buf[i] & 0xFF) << (24 - i * 8); for (int i = 0; i < 4; i++) value |= (buf[i] & 0xFF) << (24 - i * 8);
if (canonical && value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
// Value could have been encoded as an INT_16
throw new FormatException();
}
return value; return value;
} }
@@ -202,6 +214,11 @@ class BdfReaderImpl implements BdfReader {
readIntoBuffer(8); readIntoBuffer(8);
long value = 0; long value = 0;
for (int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8); for (int i = 0; i < 8; i++) value |= (buf[i] & 0xFFL) << (56 - i * 8);
if (canonical && value >= Integer.MIN_VALUE &&
value <= Integer.MAX_VALUE) {
// Value could have been encoded as an INT_32
throw new FormatException();
}
return value; return value;
} }
@@ -215,6 +232,31 @@ class BdfReaderImpl implements BdfReader {
hasLookahead = false; hasLookahead = false;
} }
@Override
public boolean hasInt() throws IOException {
if (!hasLookahead) readLookahead();
if (eof) return false;
return next == INT_8 || next == INT_16 || next == INT_32;
}
@Override
public int readInt() throws IOException {
if (!hasInt()) throw new FormatException();
hasLookahead = false;
if (next == INT_8) return readInt8();
if (next == INT_16) return readInt16();
return readInt32();
}
@Override
public void skipInt() throws IOException {
if (!hasInt()) throw new FormatException();
if (next == INT_8) skip(1);
else if (next == INT_16) skip(2);
else skip(4);
hasLookahead = false;
}
@Override @Override
public boolean hasDouble() throws IOException { public boolean hasDouble() throws IOException {
if (!hasLookahead) readLookahead(); if (!hasLookahead) readLookahead();
@@ -323,22 +365,11 @@ class BdfReaderImpl implements BdfReader {
private BdfList readList(int level) throws IOException { private BdfList readList(int level) throws IOException {
if (!hasList()) throw new FormatException(); if (!hasList()) throw new FormatException();
if (level > nestedLimit) throw new FormatException(); if (level > nestedLimit) throw new FormatException();
BdfList list = new BdfList();
readListStart();
while (!hasListEnd()) list.add(readObject(level + 1));
readListEnd();
return list;
}
@Override
public void readListStart() throws IOException {
if (!hasList()) throw new FormatException();
hasLookahead = false; hasLookahead = false;
} BdfList list = new BdfList();
while (!hasEnd()) list.add(readObject(level + 1));
@Override readEnd();
public boolean hasListEnd() throws IOException { return list;
return hasEnd();
} }
private boolean hasEnd() throws IOException { private boolean hasEnd() throws IOException {
@@ -347,11 +378,6 @@ class BdfReaderImpl implements BdfReader {
return next == END; return next == END;
} }
@Override
public void readListEnd() throws IOException {
readEnd();
}
private void readEnd() throws IOException { private void readEnd() throws IOException {
if (!hasEnd()) throw new FormatException(); if (!hasEnd()) throw new FormatException();
hasLookahead = false; hasLookahead = false;
@@ -361,7 +387,7 @@ class BdfReaderImpl implements BdfReader {
public void skipList() throws IOException { public void skipList() throws IOException {
if (!hasList()) throw new FormatException(); if (!hasList()) throw new FormatException();
hasLookahead = false; hasLookahead = false;
while (!hasListEnd()) skipObject(); while (!hasEnd()) skipObject();
hasLookahead = false; hasLookahead = false;
} }
@@ -380,35 +406,27 @@ class BdfReaderImpl implements BdfReader {
private BdfDictionary readDictionary(int level) throws IOException { private BdfDictionary readDictionary(int level) throws IOException {
if (!hasDictionary()) throw new FormatException(); if (!hasDictionary()) throw new FormatException();
if (level > nestedLimit) throw new FormatException(); if (level > nestedLimit) throw new FormatException();
BdfDictionary dictionary = new BdfDictionary();
readDictionaryStart();
while (!hasDictionaryEnd())
dictionary.put(readString(), readObject(level + 1));
readDictionaryEnd();
return dictionary;
}
@Override
public void readDictionaryStart() throws IOException {
if (!hasDictionary()) throw new FormatException();
hasLookahead = false; hasLookahead = false;
BdfDictionary dictionary = new BdfDictionary();
String prevKey = null;
while (!hasEnd()) {
String key = readString();
if (canonical && prevKey != null && key.compareTo(prevKey) <= 0) {
// Keys not unique and sorted
throw new FormatException();
} }
dictionary.put(key, readObject(level + 1));
@Override prevKey = key;
public boolean hasDictionaryEnd() throws IOException {
return hasEnd();
} }
@Override
public void readDictionaryEnd() throws IOException {
readEnd(); readEnd();
return dictionary;
} }
@Override @Override
public void skipDictionary() throws IOException { public void skipDictionary() throws IOException {
if (!hasDictionary()) throw new FormatException(); if (!hasDictionary()) throw new FormatException();
hasLookahead = false; hasLookahead = false;
while (!hasDictionaryEnd()) { while (!hasEnd()) {
skipString(); skipString();
skipObject(); skipObject();
} }

View File

@@ -2,11 +2,13 @@ package org.briarproject.bramble.data;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfWriter; import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -15,6 +17,7 @@ import java.util.Map.Entry;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static java.util.Collections.sort;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.data.Types.DICTIONARY; import static org.briarproject.bramble.data.Types.DICTIONARY;
import static org.briarproject.bramble.data.Types.END; import static org.briarproject.bramble.data.Types.END;
@@ -33,10 +36,11 @@ import static org.briarproject.bramble.data.Types.STRING_16;
import static org.briarproject.bramble.data.Types.STRING_32; import static org.briarproject.bramble.data.Types.STRING_32;
import static org.briarproject.bramble.data.Types.STRING_8; import static org.briarproject.bramble.data.Types.STRING_8;
import static org.briarproject.bramble.data.Types.TRUE; import static org.briarproject.bramble.data.Types.TRUE;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class BdfWriterImpl implements BdfWriter { final class BdfWriterImpl implements BdfWriter {
private final OutputStream out; private final OutputStream out;
@@ -113,7 +117,7 @@ class BdfWriterImpl implements BdfWriter {
@Override @Override
public void writeString(String s) throws IOException { public void writeString(String s) throws IOException {
byte[] b = s.getBytes("UTF-8"); byte[] b = s.getBytes(UTF_8);
if (b.length <= Byte.MAX_VALUE) { if (b.length <= Byte.MAX_VALUE) {
out.write(STRING_8); out.write(STRING_8);
out.write((byte) b.length); out.write((byte) b.length);
@@ -161,39 +165,33 @@ class BdfWriterImpl implements BdfWriter {
else if (o instanceof String) writeString((String) o); else if (o instanceof String) writeString((String) o);
else if (o instanceof byte[]) writeRaw((byte[]) o); else if (o instanceof byte[]) writeRaw((byte[]) o);
else if (o instanceof Bytes) writeRaw(((Bytes) o).getBytes()); else if (o instanceof Bytes) writeRaw(((Bytes) o).getBytes());
else if (o instanceof List) writeList((List) o); else if (o instanceof List) writeList((List<?>) o);
else if (o instanceof Map) writeDictionary((Map) o); else if (o instanceof Map) writeDictionary((Map<?, ?>) o);
else throw new FormatException(); else throw new FormatException();
} }
@Override
public void writeListStart() throws IOException {
out.write(LIST);
}
@Override
public void writeListEnd() throws IOException {
out.write(END);
}
@Override @Override
public void writeDictionary(Map<?, ?> m) throws IOException { public void writeDictionary(Map<?, ?> m) throws IOException {
out.write(DICTIONARY); out.write(DICTIONARY);
for (Entry<?, ?> e : m.entrySet()) { if (m instanceof BdfDictionary) {
if (!(e.getKey() instanceof String)) throw new FormatException(); // Entries are already sorted and keys are known to be strings
writeString((String) e.getKey()); for (Entry<String, Object> e : ((BdfDictionary) m).entrySet()) {
writeString(e.getKey());
writeObject(e.getValue()); writeObject(e.getValue());
} }
out.write(END); } else {
// Check that keys are strings, write entries in canonical order
List<String> keys = new ArrayList<>(m.size());
for (Object k : m.keySet()) {
if (!(k instanceof String)) throw new FormatException();
keys.add((String) k);
}
sort(keys);
for (String key : keys) {
writeString(key);
writeObject(m.get(key));
} }
@Override
public void writeDictionaryStart() throws IOException {
out.write(DICTIONARY);
} }
@Override
public void writeDictionaryEnd() throws IOException {
out.write(END); out.write(END);
} }
} }

View File

@@ -413,6 +413,9 @@ interface Database<T> {
*/ */
Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException; Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException;
Collection<String> explainGetMessageIds(T txn, GroupId g)
throws DbException;
/** /**
* Returns the IDs of any delivered messages in the given group with * Returns the IDs of any delivered messages in the given group with
* metadata that matches all entries in the given query. If the query is * metadata that matches all entries in the given query. If the query is

View File

@@ -24,7 +24,7 @@ public class DatabaseModule {
@Singleton @Singleton
Database<Connection> provideDatabase(DatabaseConfig config, Database<Connection> provideDatabase(DatabaseConfig config,
MessageFactory messageFactory, Clock clock) { MessageFactory messageFactory, Clock clock) {
return new H2Database(config, messageFactory, clock); return new SqliteDatabase(config, messageFactory, clock);
} }
@Provides @Provides

View File

@@ -4,14 +4,16 @@ class DatabaseTypes {
private final String hashType, secretType, binaryType; private final String hashType, secretType, binaryType;
private final String counterType, stringType; private final String counterType, stringType;
private final String explainCommand; // FIXME: Remove
public DatabaseTypes(String hashType, String secretType, String binaryType, public DatabaseTypes(String hashType, String secretType, String binaryType,
String counterType, String stringType) { String counterType, String stringType, String explainCommand) {
this.hashType = hashType; this.hashType = hashType;
this.secretType = secretType; this.secretType = secretType;
this.binaryType = binaryType; this.binaryType = binaryType;
this.counterType = counterType; this.counterType = counterType;
this.stringType = stringType; this.stringType = stringType;
this.explainCommand = explainCommand;
} }
/** /**
@@ -22,6 +24,7 @@ class DatabaseTypes {
* <li> _BINARY * <li> _BINARY
* <li> _COUNTER * <li> _COUNTER
* <li> _STRING * <li> _STRING
* <li> _EXPLAIN
*/ */
String replaceTypes(String s) { String replaceTypes(String s) {
s = s.replaceAll("_HASH", hashType); s = s.replaceAll("_HASH", hashType);
@@ -29,6 +32,7 @@ class DatabaseTypes {
s = s.replaceAll("_BINARY", binaryType); s = s.replaceAll("_BINARY", binaryType);
s = s.replaceAll("_COUNTER", counterType); s = s.replaceAll("_COUNTER", counterType);
s = s.replaceAll("_STRING", stringType); s = s.replaceAll("_STRING", stringType);
s = s.replaceAll("_EXPLAIN", explainCommand);
return s; return s;
} }
} }

View File

@@ -39,10 +39,13 @@ class H2Database extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)"; private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)"; private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT"; private static final String COUNTER_TYPE =
"INT NOT NULL AUTO_INCREMENT PRIMARY KEY";
private static final String STRING_TYPE = "VARCHAR"; private static final String STRING_TYPE = "VARCHAR";
private static final String EXPLAIN_COMMAND = "EXPLAIN";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE, private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE); SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
EXPLAIN_COMMAND);
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;
@@ -73,7 +76,7 @@ class H2Database extends JdbcDatabase {
boolean reopen = isNonEmptyDirectory(dir); boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen); if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory"); if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.h2.Driver", reopen, key, listener); super.open("org.h2.Driver", reopen, false, key, listener);
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory after opening DB:"); LOG.info("Contents of account directory after opening DB:");
logFileOrDir(LOG, INFO, dir.getParentFile()); logFileOrDir(LOG, INFO, dir.getParentFile());

View File

@@ -38,11 +38,13 @@ class HyperSqlDatabase extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)"; private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)"; private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = private static final String COUNTER_TYPE = "INTEGER NOT NULL"
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)"; + " PRIMARY KEY GENERATED ALWAYS AS IDENTITY(START WITH 1)";
private static final String STRING_TYPE = "VARCHAR"; private static final String STRING_TYPE = "VARCHAR";
private static final String EXPLAIN_COMMAND = "EXPLAIN PLAN FOR";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE, private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE); SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
EXPLAIN_COMMAND);
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;
@@ -70,7 +72,7 @@ class HyperSqlDatabase extends JdbcDatabase {
boolean reopen = isNonEmptyDirectory(dir); boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen); if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory"); if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener); super.open("org.hsqldb.jdbc.JDBCDriver", reopen, true, key, listener);
return reopen; return reopen;
} }

View File

@@ -143,8 +143,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " handshakePublicKey _BINARY," // Null if key is unknown + " handshakePublicKey _BINARY," // Null if key is unknown
+ " localAuthorId _HASH NOT NULL," + " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL," + " verified BOOLEAN NOT NULL,"
+ " syncVersions _BINARY DEFAULT '00' NOT NULL," + " syncVersions _BINARY DEFAULT x'00' NOT NULL,"
+ " PRIMARY KEY (contactId),"
+ " FOREIGN KEY (localAuthorId)" + " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)" + " REFERENCES localAuthors (authorId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
@@ -295,11 +294,11 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " active BOOLEAN NOT NULL," + " active BOOLEAN NOT NULL,"
+ " rootKey _SECRET," // Null for rotation keys + " rootKey _SECRET," // Null for rotation keys
+ " alice BOOLEAN," // Null for rotation keys + " alice BOOLEAN," // Null for rotation keys
+ " PRIMARY KEY (transportId, keySetId)," // FIXME: Primary key has changed, migration needed
+ " FOREIGN KEY (transportId)" + " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)" + " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE," + " ON DELETE CASCADE,"
+ " UNIQUE (keySetId)," // FIXME: Unique constraint removed, migration needed
+ " FOREIGN KEY (contactId)" + " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)" + " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE," + " ON DELETE CASCADE,"
@@ -358,6 +357,85 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline" "CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)"; + " ON messages (cleanupDeadline)";
// FIXME: Migration needs to add new index
private static final String INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID_KEY_SET_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByTransportIdKeySetId"
+ " ON outgoingKeys (transportId, keySetId)";
private static final String FOREIGN_INDEX_CONTACTS_BY_LOCAL_AUTHOR_ID =
"CREATE INDEX IF NOT EXISTS contactsByLocalAuthorId"
+ " ON contacts (localAuthorId)";
private static final String FOREIGN_INDEX_GROUP_METADATA_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS groupMetadataByGroupId"
+ " ON groupMetadata (groupId)";
private static final String FOREIGN_INDEX_GROUP_VISIBILITIES_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS groupVisibilitiesByContactId"
+ " ON groupVisibilities (contactId)";
private static final String FOREIGN_INDEX_GROUP_VISIBILITIES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS groupVisibilitiesByGroupId"
+ " ON groupVisibilities (groupId)";
private static final String FOREIGN_INDEX_MESSAGES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS messagesByGroupId"
+ " ON messages (groupId)";
private static final String FOREIGN_INDEX_MESSAGE_METADATA_BY_MESSAGE_ID =
"CREATE INDEX IF NOT EXISTS messageMetadataByMessageId"
+ " ON messageMetadata (messageId)";
private static final String FOREIGN_INDEX_MESSAGE_METADATA_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupId"
+ " ON messageMetadata (groupId)";
private static final String FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS messageDependenciesByGroupId"
+ " ON messageDependencies (groupId)";
private static final String
FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_MESSAGE_ID =
"CREATE INDEX IF NOT EXISTS messageDependenciesByMessageId"
+ " ON messageDependencies (messageId)";
private static final String FOREIGN_INDEX_OFFERS_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS offersByContactId"
+ " ON offers (contactId)";
private static final String FOREIGN_INDEX_STATUSES_BY_MESSAGE_ID =
"CREATE INDEX IF NOT EXISTS statusesByMessageId"
+ " ON statuses (messageId)";
private static final String FOREIGN_INDEX_STATUSES_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS statusesByContactId"
+ " ON statuses (contactId)";
private static final String FOREIGN_INDEX_STATUSES_BY_GROUP_ID =
"CREATE INDEX IF NOT EXISTS statusesByGroupId"
+ " ON statuses (groupId)";
private static final String FOREIGN_INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByTransportId"
+ " ON outgoingKeys (transportId)";
private static final String FOREIGN_INDEX_OUTGOING_KEYS_BY_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByContactId"
+ " ON outgoingKeys (contactId)";
private static final String
FOREIGN_INDEX_OUTGOING_KEYS_BY_PENDING_CONTACT_ID =
"CREATE INDEX IF NOT EXISTS outgoingKeysByPendingContactId"
+ " ON outgoingKeys (pendingContactId)";
private static final String FOREIGN_INDEX_INCOMING_KEYS_BY_TRANSPORT_ID =
"CREATE INDEX IF NOT EXISTS incomingKeysByTransportId"
+ " ON incomingKeys (transportId)";
private static final String FOREIGN_INDEX_INCOMING_KEYS_BY_KEY_SET_ID =
"CREATE INDEX IF NOT EXISTS incomingKeysByKeySetId"
+ " ON incomingKeys (keySetId)";
private static final Logger LOG = private static final Logger LOG =
getLogger(JdbcDatabase.class.getName()); getLogger(JdbcDatabase.class.getName());
@@ -393,6 +471,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
protected void open(String driverClass, boolean reopen, protected void open(String driverClass, boolean reopen,
boolean createForeignKeyIndexes,
@SuppressWarnings("unused") SecretKey key, @SuppressWarnings("unused") SecretKey key,
@Nullable MigrationListener listener) throws DbException { @Nullable MigrationListener listener) throws DbException {
// Load the JDBC driver // Load the JDBC driver
@@ -419,7 +498,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("db dirty? " + wasDirtyOnInitialisation); LOG.info("db dirty? " + wasDirtyOnInitialisation);
} }
createIndexes(txn); createIndexes(txn, createForeignKeyIndexes);
setDirty(txn, true); setDirty(txn, true);
commitTransaction(txn); commitTransaction(txn);
} catch (DbException e) { } catch (DbException e) {
@@ -552,7 +631,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
private void createIndexes(Connection txn) throws DbException { private void createIndexes(Connection txn, boolean createForeignKeyIndexes)
throws DbException {
Statement s = null; Statement s = null;
try { try {
s = txn.createStatement(); s = txn.createStatement();
@@ -564,6 +644,31 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE); s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
s.executeUpdate(INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID_KEY_SET_ID);
// Some DB implementations automatically create indexes on columns
// that are foreign keys, others don't
if (createForeignKeyIndexes) {
s.executeUpdate(FOREIGN_INDEX_CONTACTS_BY_LOCAL_AUTHOR_ID);
s.executeUpdate(FOREIGN_INDEX_GROUP_METADATA_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_GROUP_VISIBILITIES_BY_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_GROUP_VISIBILITIES_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGES_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGE_METADATA_BY_MESSAGE_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGE_METADATA_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_GROUP_ID);
s.executeUpdate(
FOREIGN_INDEX_MESSAGE_DEPENDENCIES_BY_MESSAGE_ID);
s.executeUpdate(FOREIGN_INDEX_OFFERS_BY_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_STATUSES_BY_MESSAGE_ID);
s.executeUpdate(FOREIGN_INDEX_STATUSES_BY_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_STATUSES_BY_GROUP_ID);
s.executeUpdate(FOREIGN_INDEX_OUTGOING_KEYS_BY_TRANSPORT_ID);
s.executeUpdate(FOREIGN_INDEX_OUTGOING_KEYS_BY_CONTACT_ID);
s.executeUpdate(
FOREIGN_INDEX_OUTGOING_KEYS_BY_PENDING_CONTACT_ID);
s.executeUpdate(FOREIGN_INDEX_INCOMING_KEYS_BY_TRANSPORT_ID);
s.executeUpdate(FOREIGN_INDEX_INCOMING_KEYS_BY_KEY_SET_ID);
}
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s, LOG, WARNING); tryToClose(s, LOG, WARNING);
@@ -1914,6 +2019,38 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Collection<String> explainGetMessageIds(Connection txn, GroupId g)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = dbTypes.replaceTypes("_EXPLAIN SELECT messageId"
+ " FROM messages"
+ " WHERE groupId = ? AND state = ?");
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
int cols = rs.getMetaData().getColumnCount();
List<String> explanation = new ArrayList<>();
while (rs.next()) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= cols; i++) {
sb.append(rs.getString(i)).append(' ');
}
explanation.add(sb.toString());
}
rs.close();
ps.close();
return explanation;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<MessageId> getMessageIds(Connection txn, GroupId g, public Collection<MessageId> getMessageIds(Connection txn, GroupId g,
Metadata query) throws DbException { Metadata query) throws DbException {
@@ -2597,6 +2734,9 @@ abstract class JdbcDatabase implements Database<Connection> {
PublicKey publicKey = new AgreementPublicKey(rs.getBytes(1)); PublicKey publicKey = new AgreementPublicKey(rs.getBytes(1));
String alias = rs.getString(2); String alias = rs.getString(2);
long timestamp = rs.getLong(3); long timestamp = rs.getLong(3);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return new PendingContact(p, publicKey, alias, timestamp); return new PendingContact(p, publicKey, alias, timestamp);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);

View File

@@ -0,0 +1,117 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbClosedException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.nullsafety.NotNullByDefault;
import org.sqlite.mc.SQLiteMCSqlCipherConfig;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
/**
* Contains all the SQLite-specific code for the database.
*/
@NotNullByDefault
class SqliteDatabase extends JdbcDatabase {
private static final Logger LOG = getLogger(SqliteDatabase.class.getName());
private static final String HASH_TYPE = "BLOB";
private static final String SECRET_TYPE = "BLOB";
private static final String BINARY_TYPE = "BLOB";
private static final String COUNTER_TYPE =
"INTEGER PRIMARY KEY AUTOINCREMENT";
private static final String STRING_TYPE = "VARCHAR";
private static final String EXPLAIN_COMMAND = "EXPLAIN QUERY PLAN";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
EXPLAIN_COMMAND);
private final DatabaseConfig config;
private final String url;
@Nullable
private volatile Properties properties = null;
@Inject
SqliteDatabase(DatabaseConfig config, MessageFactory messageFactory,
Clock clock) {
super(dbTypes, messageFactory, clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
url = "jdbc:sqlite:" + path + "?cipher=sqlcipher";
}
@Override
public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException {
properties = SQLiteMCSqlCipherConfig.getDefault()
.withHexKey(key.getBytes())
.build()
.toProperties();
File dir = config.getDatabaseDirectory();
boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.sqlite.JDBC", reopen, true, key, listener);
return reopen;
}
@Override
public void close() throws DbException {
Connection c = null;
try {
c = createConnection();
setDirty(c, false);
c.close();
closeAllConnections();
} catch (SQLException e) {
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
}
@Override
protected Connection createConnection() throws DbException, SQLException {
Properties properties = this.properties;
if (properties == null) throw new DbClosedException();
Connection c = DriverManager.getConnection(url, properties);
Statement s = null;
try {
s = c.createStatement();
s.execute("PRAGMA foreign_keys = ON");
s.execute("PRAGMA secure_delete = ON");
s.close();
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
return c;
}
@Override
protected void compactAndClose() throws DbException {
close();
}
}

View File

@@ -1,11 +1,11 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory; import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter; import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory; import org.briarproject.bramble.api.record.RecordWriterFactory;
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
Logger.getLogger(KeyAgreementTransport.class.getName()); Logger.getLogger(KeyAgreementTransport.class.getName());
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static final RecordPredicate ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfWriter; import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.api.data.BdfWriterFactory; import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
@@ -32,13 +33,14 @@ class PayloadEncoderImpl implements PayloadEncoder {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION; int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
out.write(formatIdAndVersion); out.write(formatIdAndVersion);
BdfList payload = new BdfList();
payload.add(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors()) {
payload.add(d.getDescriptor());
}
BdfWriter w = bdfWriterFactory.createWriter(out); BdfWriter w = bdfWriterFactory.createWriter(out);
try { try {
w.writeListStart(); // Payload start w.writeList(payload);
w.writeRaw(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors())
w.writeList(d.getDescriptor());
w.writeListEnd(); // Payload end
} catch (IOException e) { } catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream // Shouldn't happen with ByteArrayOutputStream
throw new AssertionError(e); throw new AssertionError(e);

View File

@@ -73,7 +73,7 @@ class PayloadParserImpl implements PayloadParser {
List<TransportDescriptor> recognised = new ArrayList<>(); List<TransportDescriptor> recognised = new ArrayList<>();
for (int i = 1; i < payload.size(); i++) { for (int i = 1; i < payload.size(); i++) {
BdfList descriptor = payload.getList(i); BdfList descriptor = payload.getList(i);
long transportId = descriptor.getLong(0); int transportId = descriptor.getInt(0);
if (transportId == TRANSPORT_ID_BLUETOOTH) { if (transportId == TRANSPORT_ID_BLUETOOTH) {
TransportId id = BluetoothConstants.ID; TransportId id = BluetoothConstants.ID;
recognised.add(new TransportDescriptor(id, descriptor)); recognised.add(new TransportDescriptor(id, descriptor));

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
@@ -11,12 +12,15 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
@@ -26,6 +30,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -98,6 +103,22 @@ class MailboxManagerImpl implements MailboxManager {
return created; return created;
} }
@Override
public String convertBase32Payload(String base32Payload)
throws FormatException {
Pattern regex = Pattern.compile("(briar-mailbox://)?([a-z2-7]{104})");
Matcher matcher = regex.matcher(base32Payload);
if (!matcher.find()) throw new FormatException();
String base32 = matcher.group(2);
byte[] payloadBytes;
try {
payloadBytes = Base32.decode(base32, false);
} catch (IllegalArgumentException e) {
throw new FormatException();
}
return new String(payloadBytes, ISO_8859_1);
}
@Override @Override
public boolean checkConnection() { public boolean checkConnection() {
List<MailboxVersion> versions = null; List<MailboxVersion> versions = null;

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.data.MetadataEncoder;
@@ -76,14 +75,11 @@ public class MailboxModule {
ValidationManager validationManager, ValidationManager validationManager,
ClientHelper clientHelper, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, MetadataEncoder metadataEncoder,
Clock clock, Clock clock) {
FeatureFlags featureFlags) {
MailboxUpdateValidator validator = new MailboxUpdateValidator( MailboxUpdateValidator validator = new MailboxUpdateValidator(
clientHelper, metadataEncoder, clock); clientHelper, metadataEncoder, clock);
if (featureFlags.shouldEnableMailbox()) { validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
validationManager.registerMessageValidator(CLIENT_ID, validator);
MAJOR_VERSION, validator);
}
return validator; return validator;
} }
@@ -95,31 +91,26 @@ public class MailboxModule {
@Provides @Provides
@Singleton @Singleton
MailboxUpdateManager provideMailboxUpdateManager( MailboxUpdateManager provideMailboxUpdateManager(
FeatureFlags featureFlags,
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
ValidationManager validationManager, ContactManager contactManager, ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManagerImpl mailboxUpdateManager) { MailboxUpdateManagerImpl mailboxUpdateManager) {
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager); lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
MAJOR_VERSION, mailboxUpdateManager); mailboxUpdateManager);
contactManager.registerContactHook(mailboxUpdateManager); contactManager.registerContactHook(mailboxUpdateManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION, clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, mailboxUpdateManager); MINOR_VERSION, mailboxUpdateManager);
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager); mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
}
return mailboxUpdateManager; return mailboxUpdateManager;
} }
@Provides @Provides
@Singleton @Singleton
MailboxFileManager provideMailboxFileManager(FeatureFlags featureFlags, MailboxFileManager provideMailboxFileManager(EventBus eventBus,
EventBus eventBus, MailboxFileManagerImpl mailboxFileManager) { MailboxFileManagerImpl mailboxFileManager) {
if (featureFlags.shouldEnableMailbox()) {
eventBus.addListener(mailboxFileManager); eventBus.addListener(mailboxFileManager);
}
return mailboxFileManager; return mailboxFileManager;
} }
@@ -160,17 +151,14 @@ public class MailboxModule {
MailboxUpdateManager mailboxUpdateManager, MailboxUpdateManager mailboxUpdateManager,
MailboxClientFactory mailboxClientFactory, MailboxClientFactory mailboxClientFactory,
TorReachabilityMonitor reachabilityMonitor, TorReachabilityMonitor reachabilityMonitor,
FeatureFlags featureFlags,
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
EventBus eventBus) { EventBus eventBus) {
MailboxClientManager manager = new MailboxClientManager(eventExecutor, MailboxClientManager manager = new MailboxClientManager(eventExecutor,
dbExecutor, db, contactManager, pluginManager, dbExecutor, db, contactManager, pluginManager,
mailboxSettingsManager, mailboxUpdateManager, mailboxSettingsManager, mailboxUpdateManager,
mailboxClientFactory, reachabilityMonitor); mailboxClientFactory, reachabilityMonitor);
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerService(manager); lifecycleManager.registerService(manager);
eventBus.addListener(manager); eventBus.addListener(manager);
}
return manager; return manager;
} }
} }

View File

@@ -40,9 +40,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
@@ -288,8 +288,10 @@ class PluginManagerImpl implements PluginManager, Service {
private class Callback implements PluginCallback { private class Callback implements PluginCallback {
private final TransportId id; private final TransportId id;
private final AtomicReference<State> state = private final Object stateLock = new Object();
new AtomicReference<>(STARTING_STOPPING);
@GuardedBy("lock")
private State state = STARTING_STOPPING;
private Callback(TransportId id) { private Callback(TransportId id) {
this.id = id; this.id = id;
@@ -343,8 +345,10 @@ class PluginManagerImpl implements PluginManager, Service {
@Override @Override
public void pluginStateChanged(State newState) { public void pluginStateChanged(State newState) {
State oldState = state.getAndSet(newState); synchronized (stateLock) {
if (newState != oldState) { if (newState != state) {
State oldState = state;
state = newState;
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(id + " changed from state " + oldState LOG.info(id + " changed from state " + oldState
+ " to " + newState); + " to " + newState);
@@ -356,11 +360,13 @@ class PluginManagerImpl implements PluginManager, Service {
eventBus.broadcast(new TransportInactiveEvent(id)); eventBus.broadcast(new TransportInactiveEvent(id));
} }
} else if (newState == DISABLED) { } else if (newState == DISABLED) {
// Broadcast an event even though the state hasn't changed, as // Broadcast an event even though the state hasn't changed,
// the reasons for the plugin being disabled may have changed // as the reasons for the plugin being disabled may have
// changed
eventBus.broadcast(new TransportStateEvent(id, newState)); eventBus.broadcast(new TransportStateEvent(id, newState));
} }
} }
}
@Override @Override
public void handleConnection(DuplexTransportConnection d) { public void handleConnection(DuplexTransportConnection d) {

View File

@@ -419,7 +419,7 @@ class LanTcpPlugin extends TcpPlugin {
private InetSocketAddress parseSocketAddress(BdfList descriptor) private InetSocketAddress parseSocketAddress(BdfList descriptor)
throws FormatException { throws FormatException {
byte[] address = descriptor.getRaw(1); byte[] address = descriptor.getRaw(1);
int port = descriptor.getLong(2).intValue(); int port = descriptor.getInt(2);
if (port < 1 || port > MAX_16_BIT_UNSIGNED) throw new FormatException(); if (port < 1 || port > MAX_16_BIT_UNSIGNED) throw new FormatException();
try { try {
InetAddress addr = InetAddress.getByAddress(address); InetAddress addr = InetAddress.getByAddress(address);

View File

@@ -1,5 +1,8 @@
package org.briarproject.bramble.plugin.tor; package org.briarproject.bramble.plugin.tor;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.CircumventionProviderFactory;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -10,8 +13,7 @@ public class CircumventionModule {
@Provides @Provides
@Singleton @Singleton
CircumventionProvider provideCircumventionProvider( CircumventionProvider provideCircumventionProvider() {
CircumventionProviderImpl provider) { return CircumventionProviderFactory.createCircumventionProvider();
return provider;
} }
} }

View File

@@ -1,75 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import java.util.List;
@NotNullByDefault
public interface CircumventionProvider {
enum BridgeType {
DEFAULT_OBFS4,
NON_DEFAULT_OBFS4,
VANILLA,
MEEK,
SNOWFLAKE
}
/**
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
* <p>
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/
String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #DPI_BRIDGES}.
*/
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] DEFAULT_BRIDGES = {"EG", "VE"};
/**
* Countries where non-default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NON_DEFAULT_BRIDGES = {"BY", "RU"};
/**
* Countries where vanilla bridges are blocked via DPI but non-default
* obfs4 bridges, meek and snowflake may work. Should be a subset of
* {@link #BRIDGES}.
*/
String[] DPI_BRIDGES = {"CN", "IR", "TM"};
/**
* Returns true if vanilla Tor connections are blocked in the given country.
*/
boolean isTorProbablyBlocked(String countryCode);
/**
* Returns true if bridge connections of some type work in the given
* country.
*/
boolean doBridgesWork(String countryCode);
/**
* Returns the types of bridge connection that are suitable for the given
* country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
* to work.
*/
List<BridgeType> getSuitableBridgeTypes(String countryCode);
@IoExecutor
List<String> getBridges(BridgeType type, String countryCode,
boolean letsEncrypt);
}

View File

@@ -1,129 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.nullsafety.NotNullByDefault;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.nullsafety.NullSafety.requireNonNull;
@Immutable
@NotNullByDefault
class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges";
private final static String SNOWFLAKE_PARAMS_FILE_NAME = "snowflake-params";
private final static String DEFAULT_COUNTRY_CODE = "ZZ";
private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(asList(BLOCKED));
private static final Set<String> BRIDGE_COUNTRIES =
new HashSet<>(asList(BRIDGES));
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> DPI_COUNTRIES =
new HashSet<>(asList(DPI_BRIDGES));
@Inject
CircumventionProviderImpl() {
}
@Override
public boolean isTorProbablyBlocked(String countryCode) {
return BLOCKED_IN_COUNTRIES.contains(countryCode);
}
@Override
public boolean doBridgesWork(String countryCode) {
return BRIDGE_COUNTRIES.contains(countryCode);
}
@Override
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK, SNOWFLAKE);
} else {
return asList(DEFAULT_OBFS4, VANILLA);
}
}
@Override
@IoExecutor
public List<String> getBridges(BridgeType type, String countryCode,
boolean letsEncrypt) {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME));
Scanner scanner = new Scanner(is);
List<String> bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
(type == VANILLA && line.startsWith("v ")) ||
(type == MEEK && line.startsWith("m "))) {
bridges.add(line.substring(2));
} else if (type == SNOWFLAKE && line.startsWith("s ")) {
String params = getSnowflakeParams(countryCode, letsEncrypt);
bridges.add(line.substring(2) + " " + params);
}
}
scanner.close();
return bridges;
}
// Package access for testing
@SuppressWarnings("WeakerAccess")
String getSnowflakeParams(String countryCode, boolean letsEncrypt) {
Map<String, String> params = loadSnowflakeParams();
if (countryCode.isEmpty()) countryCode = DEFAULT_COUNTRY_CODE;
// If we have parameters for this country code, return them
String value = params.get(makeKey(countryCode, letsEncrypt));
if (value != null) return value;
// Return the default parameters
value = params.get(makeKey(DEFAULT_COUNTRY_CODE, letsEncrypt));
return requireNonNull(value);
}
private Map<String, String> loadSnowflakeParams() {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(SNOWFLAKE_PARAMS_FILE_NAME));
Scanner scanner = new Scanner(is);
Map<String, String> params = new TreeMap<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.length() < 5) continue;
String key = line.substring(0, 4); // Country code, space, digit
String value = line.substring(5);
params.put(key, value);
}
scanner.close();
return params;
}
private String makeKey(String countryCode, boolean letsEncrypt) {
return countryCode + " " + (letsEncrypt ? "1" : "0");
}
}

View File

@@ -1,8 +1,5 @@
package org.briarproject.bramble.plugin.tor; package org.briarproject.bramble.plugin.tor;
import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
@@ -27,35 +24,27 @@ import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.nullsafety.InterfaceNotNullByDefault;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType;
import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.CircumventionProvider.BridgeType;
import org.briarproject.onionwrapper.LocationUtils;
import org.briarproject.onionwrapper.TorWrapper;
import org.briarproject.onionwrapper.TorWrapper.HiddenServiceProperties;
import org.briarproject.onionwrapper.TorWrapper.Observer;
import org.briarproject.onionwrapper.TorWrapper.TorState;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
@@ -64,13 +53,9 @@ import javax.net.SocketFactory;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
@@ -92,35 +77,19 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA; import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.SNOWFLAKE;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES; import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion; import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.onionwrapper.CircumventionProvider.BridgeType.SNOWFLAKE;
@MethodsNotNullByDefault @InterfaceNotNullByDefault
@ParametersNotNullByDefault class TorPlugin implements DuplexPlugin, EventListener {
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
protected static final Logger LOG = getLogger(TorPlugin.class.getName()); protected static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = {
"CIRC",
"ORCONN",
"STATUS_GENERAL",
"STATUS_CLIENT",
"HS_DESC",
"NOTICE",
"WARN",
"ERR"
};
private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}"); private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
protected final Executor ioExecutor; protected final Executor ioExecutor;
@@ -129,91 +98,79 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final NetworkManager networkManager; private final NetworkManager networkManager;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final Clock clock; private final CircumventionProvider circumventionProvider;
private final BatteryManager batteryManager; private final BatteryManager batteryManager;
private final Backoff backoff; private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto; private final TorRendezvousCrypto torRendezvousCrypto;
private final TorWrapper tor;
private final PluginCallback callback; private final PluginCallback callback;
private final String architecture;
private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider;
private final long maxLatency; private final long maxLatency;
private final int maxIdleTime; private final int maxIdleTime;
private final boolean canVerifyLetsEncryptCerts;
private final int socketTimeout; private final int socketTimeout;
private final File torDirectory;
private final File configFile;
private final int torSocksPort;
private final int torControlPort;
private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState(); protected final PluginState state = new PluginState();
private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null; private volatile Settings settings = null;
protected abstract int getProcessId();
protected abstract long getLastUpdateTime();
TorPlugin(Executor ioExecutor, TorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor, Executor wakefulIoExecutor,
NetworkManager networkManager, NetworkManager networkManager,
LocationUtils locationUtils, LocationUtils locationUtils,
SocketFactory torSocketFactory, SocketFactory torSocketFactory,
Clock clock,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
Backoff backoff, Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
TorWrapper tor,
PluginCallback callback, PluginCallback callback,
String architecture,
long maxLatency, long maxLatency,
int maxIdleTime, int maxIdleTime,
File torDirectory, boolean canVerifyLetsEncryptCerts) {
int torSocksPort,
int torControlPort) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor; this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager; this.networkManager = networkManager;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.clock = clock;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider; this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
this.backoff = backoff; this.backoff = backoff;
this.torRendezvousCrypto = torRendezvousCrypto; this.torRendezvousCrypto = torRendezvousCrypto;
this.tor = tor;
this.callback = callback; this.callback = callback;
this.architecture = architecture;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime; this.maxIdleTime = maxIdleTime;
if (maxIdleTime > Integer.MAX_VALUE / 2) this.canVerifyLetsEncryptCerts = canVerifyLetsEncryptCerts;
if (maxIdleTime > Integer.MAX_VALUE / 2) {
socketTimeout = Integer.MAX_VALUE; socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2; } else {
this.torDirectory = torDirectory; socketTimeout = maxIdleTime * 2;
this.torSocksPort = torSocksPort; }
this.torControlPort = torControlPort;
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
// Don't execute more than one connection status check at a time // Don't execute more than one connection status check at a time
connectionStatusExecutor = connectionStatusExecutor =
new PoliteExecutor("TorPlugin", ioExecutor, 1); new PoliteExecutor("TorPlugin", ioExecutor, 1);
tor.setObserver(new Observer() {
@Override
public void onState(TorState torState) {
State s = state.getState(torState);
if (s == ACTIVE) backoff.reset();
callback.pluginStateChanged(s);
} }
protected File getTorExecutableFile() { @Override
return new File(torDirectory, "tor"); public void onBootstrapPercentage(int percentage) {
} }
protected File getObfs4ExecutableFile() { @Override
return new File(torDirectory, "obfs4proxy"); public void onHsDescriptorUpload(String onion) {
} }
protected File getSnowflakeExecutableFile() { @Override
return new File(torDirectory, "snowflake"); public void onClockSkewDetected(long skewSeconds) {
}
});
} }
@Override @Override
@@ -234,89 +191,18 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) {
LOG.warning("Could not create Tor directory.");
throw new PluginException();
}
}
// Load the settings // Load the settings
settings = callback.getSettings(); settings = callback.getSettings();
// Start Tor
try { try {
// Install or update the assets if necessary tor.start();
if (!assetsAreUpToDate()) installAssets();
// Start from the default config every time
extract(getConfigInputStream(), configFile);
} catch (IOException e) {
throw new PluginException(e);
}
if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted");
// Start a new Tor process
LOG.info("Starting Tor");
File torFile = getTorExecutableFile();
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(getProcessId());
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);
pb.redirectErrorStream(true);
try {
torProcess = pb.start();
} catch (SecurityException | IOException e) {
throw new PluginException(e);
}
try {
// Wait for the Tor process to start
waitForTorToStart(torProcess);
// Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) {
if (clock.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
LOG.warning("Auth cookie not created");
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
throw new PluginException();
}
//noinspection BusyWait
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
}
LOG.info("Auth cookie created");
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.warning("Interrupted while starting Tor"); LOG.warning("Interrupted while starting Tor");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new PluginException(); throw new PluginException();
}
try {
// Open a control connection and authenticate using the cookie file
controlSocket = new Socket("127.0.0.1", torControlPort);
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(singletonList(OWNER));
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS));
// Check whether Tor has already bootstrapped
String info = controlConnection.getInfo("status/bootstrap-phase");
if (info != null && info.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
state.setBootstrapped();
}
// Check whether Tor has already built a circuit
info = controlConnection.getInfo("status/circuit-established");
if ("1".equals(info)) {
LOG.info("Tor has already built a circuit");
state.setCircuitBuilt(true);
}
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
state.setStarted();
// Check whether we're online // Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(), updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging()); batteryManager.isCharging());
@@ -324,140 +210,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind(); bind();
} }
private boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime();
}
private void installAssets() throws IOException {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
installTorExecutable();
installObfs4Executable();
installSnowflakeExecutable();
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
}
protected void extract(InputStream in, File dest) throws IOException {
OutputStream out = new FileOutputStream(dest);
copyAndClose(in, out);
}
protected void installTorExecutable() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing Tor binary for " + architecture);
File torFile = getTorExecutableFile();
extract(getTorInputStream(), torFile);
if (!torFile.setExecutable(true, true)) throw new IOException();
}
protected void installObfs4Executable() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing obfs4proxy binary for " + architecture);
File obfs4File = getObfs4ExecutableFile();
extract(getObfs4InputStream(), obfs4File);
if (!obfs4File.setExecutable(true, true)) throw new IOException();
}
protected void installSnowflakeExecutable() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing snowflake binary for " + architecture);
File snowflakeFile = getSnowflakeExecutableFile();
extract(getSnowflakeInputStream(), snowflakeFile);
if (!snowflakeFile.setExecutable(true, true)) throw new IOException();
}
private InputStream getTorInputStream() throws IOException {
return getZipInputStream("tor");
}
private InputStream getObfs4InputStream() throws IOException {
return getZipInputStream("obfs4proxy");
}
private InputStream getSnowflakeInputStream() throws IOException {
return getZipInputStream("snowflake");
}
private InputStream getZipInputStream(String basename) throws IOException {
InputStream in = resourceProvider
.getResourceInputStream(basename + "_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in);
if (zin.getNextEntry() == null) throw new IOException();
return zin;
}
private static void append(StringBuilder strb, String name, Object value) {
strb.append(name);
strb.append(" ");
strb.append(value);
strb.append("\n");
}
private InputStream getConfigInputStream() {
File dataDirectory = new File(torDirectory, ".tor");
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n");
append(strb, "ConnectionPadding", 0);
String obfs4Path = getObfs4ExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin obfs4 exec", obfs4Path);
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
}
private void listFiles(File f) {
if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null) for (File child : children) listFiles(child);
} else {
LOG.info(f.getAbsolutePath() + " " + f.length());
}
}
private byte[] read(File f) throws IOException {
byte[] b = new byte[(int) f.length()];
FileInputStream in = new FileInputStream(f);
try {
int offset = 0;
while (offset < b.length) {
int read = in.read(b, offset, b.length - offset);
if (read == -1) throw new EOFException();
offset += read;
}
return b;
} finally {
tryToClose(in, LOG, WARNING);
}
}
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, PluginException {
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Read the process's stdout (and redirected stderr) until it detaches
while (stdout.hasNextLine()) stdout.nextLine();
stdout.close();
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
}
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it // If there's already a port number stored in config, reuse it
@@ -481,9 +233,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return; return;
} }
// Store the port number // Store the port number
String localPort = String.valueOf(ss.getLocalPort()); int localPort = ss.getLocalPort();
Settings s = new Settings(); Settings s = new Settings();
s.put(PREF_TOR_PORT, localPort); s.put(PREF_TOR_PORT, String.valueOf(localPort));
callback.mergeSettings(s); callback.mergeSettings(s);
// Create a hidden service if necessary // Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort)); ioExecutor.execute(() -> publishHiddenService(localPort));
@@ -493,48 +245,28 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}); });
} }
private void publishHiddenService(String port) { private void publishHiddenService(int localPort) {
if (!state.isTorRunning()) return; if (!tor.isTorRunning()) return;
String privKey3 = settings.get(HS_PRIVATE_KEY_V3); String privKey = settings.get(HS_PRIVATE_KEY_V3);
publishV3HiddenService(port, privKey3);
}
private void publishV3HiddenService(String port, @Nullable String privKey) {
LOG.info("Creating v3 hidden service"); LOG.info("Creating v3 hidden service");
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port); HiddenServiceProperties hsProps;
Map<String, String> response;
try { try {
// Use the control connection to set up the hidden service hsProps = tor.publishHiddenService(localPort, 80, privKey);
if (privKey == null) {
response = controlConnection.addOnion("NEW:ED25519-V3",
portLines, null);
} else {
response = controlConnection.addOnion(privKey, portLines);
}
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return; return;
} }
if (!response.containsKey(HS_ADDRESS)) {
LOG.warning("Tor did not return a hidden service address");
return;
}
if (privKey == null && !response.containsKey(HS_PRIVKEY)) {
LOG.warning("Tor did not return a private key");
return;
}
String onion3 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("V3 hidden service " + scrubOnion(onion3)); LOG.info("V3 hidden service " + scrubOnion(hsProps.onion));
} }
if (privKey == null) { if (privKey == null) {
// Publish the hidden service's onion hostname in transport props // Publish the hidden service's onion hostname in transport props
TransportProperties p = new TransportProperties(); TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V3, onion3); p.put(PROP_ONION_V3, hsProps.onion);
callback.mergeLocalProperties(p); callback.mergeLocalProperties(p);
// Save the hidden service's private key for next time // Save the hidden service's private key for next time
Settings s = new Settings(); Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY)); s.put(HS_PRIVATE_KEY_V3, hsProps.privKey);
callback.mergeSettings(s); callback.mergeSettings(s);
} }
} }
@@ -557,50 +289,31 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
protected void enableNetwork(boolean enable) throws IOException {
if (!state.enableNetwork(enable)) return; // Unchanged
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
}
private void enableBridges(List<BridgeType> bridgeTypes, String countryCode) private void enableBridges(List<BridgeType> bridgeTypes, String countryCode)
throws IOException { throws IOException {
if (!state.setBridgeTypes(bridgeTypes)) return; // Unchanged
if (bridgeTypes.isEmpty()) { if (bridgeTypes.isEmpty()) {
controlConnection.setConf("UseBridges", "0"); tor.disableBridges();
controlConnection.resetConf(singletonList("Bridge"));
} else { } else {
Collection<String> conf = new ArrayList<>(); List<String> bridges = new ArrayList<>();
conf.add("UseBridges 1");
boolean letsEncrypt = canVerifyLetsEncryptCerts();
for (BridgeType bridgeType : bridgeTypes) { for (BridgeType bridgeType : bridgeTypes) {
conf.addAll(circumventionProvider bridges.addAll(circumventionProvider.getBridges(bridgeType,
.getBridges(bridgeType, countryCode, letsEncrypt)); countryCode, canVerifyLetsEncryptCerts));
} }
controlConnection.setConf(conf); tor.enableBridges(bridges);
} }
} }
/**
* Returns true if this device can verify Let's Encrypt certificates signed
* with the IdentTrust DST Root X3 certificate, which expired at the end of
* September 2021.
*/
protected boolean canVerifyLetsEncryptCerts() {
return true;
}
@Override @Override
public void stop() { public void stop() {
ServerSocket ss = state.setStopped(); ServerSocket ss = state.setStopped();
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
if (controlSocket != null && controlConnection != null) {
try { try {
LOG.info("Stopping Tor"); tor.stop();
controlConnection.shutdownTor("TERM");
controlSocket.close();
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} } catch (InterruptedException e) {
LOG.warning("Interrupted while stopping Tor");
Thread.currentThread().interrupt();
} }
} }
@@ -711,6 +424,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
TransportProperties remoteProperties = new TransportProperties(); TransportProperties remoteProperties = new TransportProperties();
remoteProperties.put(PROP_ONION_V3, remoteOnion); remoteProperties.put(PROP_ONION_V3, remoteOnion);
try { try {
@SuppressWarnings("resource")
ServerSocket ss = new ServerSocket(); ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress("127.0.0.1", 0)); ss.bind(new InetSocketAddress("127.0.0.1", 0));
int port = ss.getLocalPort(); int port = ss.getLocalPort();
@@ -727,9 +441,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Rendezvous server socket closed"); LOG.info("Rendezvous server socket closed");
} }
}); });
Map<Integer, String> portLines = tor.publishHiddenService(port, 80, blob);
singletonMap(80, "127.0.0.1:" + port);
controlConnection.addOnion(blob, portLines);
return new RendezvousEndpoint() { return new RendezvousEndpoint() {
@Override @Override
@@ -739,9 +451,12 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
controlConnection.delOnion(localOnion); try {
tor.removeHiddenService(localOnion);
} finally {
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
} }
}
}; };
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
@@ -749,121 +464,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
@Override
public void circuitStatus(String status, String id, String path) {
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
// DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && state.setCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
}
@Override
public void streamStatus(String status, String id, String target) {
}
@Override
public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CONNECTED")) state.onOrConnectionConnected();
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
}
@Override
public void bandwidthUsed(long read, long written) {
}
@Override
public void newDescriptors(List<String> orList) {
}
@Override
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
}
@Override
public void unrecognized(String type, String msg) {
if (type.equals("STATUS_CLIENT")) {
handleClientStatus(removeSeverity(msg));
} else if (type.equals("STATUS_GENERAL")) {
handleGeneralStatus(removeSeverity(msg));
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
String[] parts = msg.split(" ");
if (parts.length < 2) {
LOG.warning("Failed to parse HS_DESC UPLOADED event");
} else if (LOG.isLoggable(INFO)) {
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
}
}
}
@Override
public void controlConnectionClosed() {
if (state.isTorRunning()) {
// TODO: Restart the Tor process
LOG.warning("Control connection closed");
}
}
private String removeSeverity(String msg) {
return msg.replaceFirst("[^ ]+ ", "");
}
private void handleClientStatus(String msg) {
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
LOG.info("Bootstrapped");
state.setBootstrapped();
backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (state.setCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.setCircuitBuilt(false)) {
LOG.info("Circuit not built");
// TODO: Disable and re-enable network to prompt Tor to rebuild
// its guard/bridge connections? This will also close any
// established circuits, which might still be functioning
}
}
}
private void handleGeneralStatus(String msg) {
if (msg.startsWith("CLOCK_JUMPED")) {
Long time = parseLongArgument(msg, "TIME");
if (time != null && LOG.isLoggable(WARNING)) {
LOG.warning("Clock jumped " + time + " seconds");
}
} else if (msg.startsWith("CLOCK_SKEW")) {
Long skew = parseLongArgument(msg, "SKEW");
if (skew != null && LOG.isLoggable(WARNING)) {
LOG.warning("Clock is skewed by " + skew + " seconds");
}
}
}
@Nullable
private Long parseLongArgument(String msg, String argName) {
String[] args = msg.split(" ");
for (String arg : args) {
if (arg.startsWith(argName + "=")) {
try {
return Long.parseLong(arg.substring(argName.length() + 1));
} catch (NumberFormatException e) {
break;
}
}
}
if (LOG.isLoggable(WARNING)) {
LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
}
return null;
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) { if (e instanceof SettingsUpdatedEvent) {
@@ -886,7 +486,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus(NetworkStatus status, private void updateConnectionStatus(NetworkStatus status,
boolean charging) { boolean charging) {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
if (!state.isTorRunning()) return; if (!tor.isTorRunning()) return;
boolean online = status.isConnected(); boolean online = status.isConnected();
boolean wifi = status.isWifi(); boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only(); boolean ipv6Only = status.isIpv6Only();
@@ -970,41 +570,22 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try { try {
if (enableNetwork) { if (enableNetwork) {
enableBridges(bridgeTypes, country); enableBridges(bridgeTypes, country);
enableConnectionPadding(enableConnectionPadding); tor.enableConnectionPadding(enableConnectionPadding);
enableIpv6(ipv6Only); tor.enableIpv6(ipv6Only);
} }
enableNetwork(enableNetwork); tor.enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
}); });
} }
private void enableConnectionPadding(boolean enable) throws IOException {
if (!state.enableConnectionPadding(enable)) return; // Unchanged
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
}
private void enableIpv6(boolean enable) throws IOException {
if (!state.enableIpv6(enable)) return; // Unchanged
controlConnection.setConf("ClientUseIPv4", enable ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", enable ? "1" : "0");
}
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
protected class PluginState { private class PluginState {
@GuardedBy("this") @GuardedBy("this")
private boolean started = false, private boolean settingsChecked = false;
stopped = false,
networkInitialised = false,
networkEnabled = false,
paddingEnabled = false,
ipv6Enabled = false,
bootstrapped = false,
circuitBuilt = false,
settingsChecked = false;
@GuardedBy("this") @GuardedBy("this")
private int reasonsDisabled = 0; private int reasonsDisabled = 0;
@@ -1013,84 +594,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Nullable @Nullable
private ServerSocket serverSocket = null; private ServerSocket serverSocket = null;
@GuardedBy("this")
private int orConnectionsConnected = 0;
@GuardedBy("this")
private List<BridgeType> bridgeTypes = emptyList();
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private synchronized boolean isTorRunning() {
return started && !stopped;
}
@Nullable @Nullable
private synchronized ServerSocket setStopped() { private synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket; ServerSocket ss = serverSocket;
serverSocket = null; serverSocket = null;
callback.pluginStateChanged(getState());
return ss; return ss;
} }
private synchronized void setBootstrapped() {
boolean wasBootstrapped = bootstrapped;
bootstrapped = true;
if (!wasBootstrapped) callback.pluginStateChanged(getState());
}
/**
* Sets the `circuitBuilt` flag and returns true if the flag has
* changed.
*/
private synchronized boolean setCircuitBuilt(boolean built) {
if (built == circuitBuilt) return false; // Unchanged
circuitBuilt = built;
callback.pluginStateChanged(getState());
return true; // Changed
}
/**
* Sets the `networkEnabled` flag and returns true if the flag has
* changed.
*/
private synchronized boolean enableNetwork(boolean enable) {
boolean wasInitialised = networkInitialised;
boolean wasEnabled = networkEnabled;
networkInitialised = true;
networkEnabled = enable;
if (!enable) circuitBuilt = false;
if (!wasInitialised || enable != wasEnabled) {
callback.pluginStateChanged(getState());
}
return enable != wasEnabled;
}
/**
* Sets the `paddingEnabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableConnectionPadding(boolean enable) {
if (enable == paddingEnabled) return false; // Unchanged
paddingEnabled = enable;
return true; // Changed
}
/**
* Sets the `ipv6Enabled` flag and returns true if the flag has
* changed. Doesn't affect getState().
*/
private synchronized boolean enableIpv6(boolean enable) {
if (enable == ipv6Enabled) return false; // Unchanged
ipv6Enabled = enable;
return true; // Changed
}
private synchronized void setReasonsDisabled(int reasons) { private synchronized void setReasonsDisabled(int reasons) {
boolean wasChecked = settingsChecked; boolean wasChecked = settingsChecked;
settingsChecked = true; settingsChecked = true;
@@ -1103,7 +613,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Doesn't affect getState() // Doesn't affect getState()
private synchronized boolean setServerSocket(ServerSocket ss) { private synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false; if (serverSocket != null || !tor.isTorRunning()) return false;
serverSocket = ss; serverSocket = ss;
return true; return true;
} }
@@ -1113,57 +623,33 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (serverSocket == ss) serverSocket = null; if (serverSocket == ss) serverSocket = null;
} }
/** private synchronized State getState() {
* Sets the list of bridge types being used and returns true if the return getState(tor.getTorState());
* list has changed. The list is empty if bridges are disabled.
* Doesn't affect getState().
*/
private synchronized boolean setBridgeTypes(List<BridgeType> types) {
if (types.equals(bridgeTypes)) return false; // Unchanged
bridgeTypes = types;
return true; // Changed
} }
private synchronized State getState() { private synchronized State getState(TorState torState) {
if (!started || stopped || !settingsChecked) { // Treat TorState.STARTED as State.STARTING_STOPPING because it's
// only seen during startup, before TorWrapper#enableNetwork() is
// called for the first time. TorState.NOT_STARTED and
// TorState.STOPPED are mapped to State.STARTING_STOPPING because
// that's the State before we've started and after we've stopped.
if (torState == TorState.NOT_STARTED ||
torState == TorState.STARTING ||
torState == TorState.STARTED ||
torState == TorState.STOPPING ||
torState == TorState.STOPPED ||
!settingsChecked) {
return STARTING_STOPPING; return STARTING_STOPPING;
} }
if (reasonsDisabled != 0) return DISABLED; if (reasonsDisabled != 0) return DISABLED;
if (!networkInitialised) return ENABLING; if (torState == TorState.CONNECTING) return ENABLING;
if (!networkEnabled) return INACTIVE; if (torState == TorState.CONNECTED) return ACTIVE;
return bootstrapped && circuitBuilt && orConnectionsConnected > 0 // The plugin is enabled in settings but the device is offline
? ACTIVE : ENABLING; return INACTIVE;
} }
private synchronized int getReasonsDisabled() { private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0; return getState() == DISABLED ? reasonsDisabled : 0;
} }
private synchronized void onOrConnectionConnected() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected++;
logOrConnections();
if (oldConnected == 0) callback.pluginStateChanged(getState());
}
private synchronized void onOrConnectionClosed() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected--;
if (orConnectionsConnected < 0) {
LOG.warning("Count was zero before connection closed");
orConnectionsConnected = 0;
}
logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) {
callback.pluginStateChanged(getState());
}
}
@GuardedBy("this")
private void logOrConnections() {
if (LOG.isLoggable(INFO)) {
LOG.info(orConnectionsConnected + " OR connections connected");
}
}
} }
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -16,10 +17,10 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor; import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import org.briarproject.onionwrapper.CircumventionProvider;
import org.briarproject.onionwrapper.LocationUtils;
import java.io.File; import java.io.File;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -45,13 +46,12 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
protected final Executor ioExecutor, wakefulIoExecutor; protected final Executor ioExecutor, eventExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager; protected final NetworkManager networkManager;
protected final LocationUtils locationUtils; protected final LocationUtils locationUtils;
protected final EventBus eventBus; protected final EventBus eventBus;
protected final SocketFactory torSocketFactory; protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory; protected final BackoffFactory backoffFactory;
protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider; protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager; protected final BatteryManager batteryManager;
protected final Clock clock; protected final Clock clock;
@@ -61,13 +61,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
protected final int torControlPort; protected final int torControlPort;
TorPluginFactory(@IoExecutor Executor ioExecutor, TorPluginFactory(@IoExecutor Executor ioExecutor,
@EventExecutor Executor eventExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager, NetworkManager networkManager,
LocationUtils locationUtils, LocationUtils locationUtils,
EventBus eventBus, EventBus eventBus,
SocketFactory torSocketFactory, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
BatteryManager batteryManager, BatteryManager batteryManager,
Clock clock, Clock clock,
@@ -76,13 +76,13 @@ abstract class TorPluginFactory implements DuplexPluginFactory {
@TorSocksPort int torSocksPort, @TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) { @TorControlPort int torControlPort) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.eventExecutor = eventExecutor;
this.wakefulIoExecutor = wakefulIoExecutor; this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager; this.networkManager = networkManager;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.eventBus = eventBus; this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory; this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider; this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager; this.batteryManager = batteryManager;
this.clock = clock; this.clock = clock;

View File

@@ -201,7 +201,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
// Retrieve and parse the latest local properties // Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) { for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
BdfList message = clientHelper.getMessageAsList(txn, BdfList message = clientHelper.getMessageAsList(txn,
e.getValue().messageId); e.getValue().messageId, false);
local.put(e.getKey(), parseProperties(message)); local.put(e.getKey(), parseProperties(message));
} }
return local; return local;
@@ -222,7 +222,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (latest != null) { if (latest != null) {
// Retrieve and parse the latest local properties // Retrieve and parse the latest local properties
BdfList message = clientHelper.getMessageAsList(txn, BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId); latest.messageId, false);
p = parseProperties(message); p = parseProperties(message);
} }
return p == null ? new TransportProperties() : p; return p == null ? new TransportProperties() : p;
@@ -252,7 +252,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
local = new TransportProperties(); local = new TransportProperties();
} else { } else {
BdfList message = clientHelper.getMessageAsList(txn, BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId); latest.messageId, false);
local = parseProperties(message); local = parseProperties(message);
} }
storeLocalProperties(txn, c, t, local); storeLocalProperties(txn, c, t, local);
@@ -272,8 +272,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
remote = new TransportProperties(); remote = new TransportProperties();
} else { } else {
// Retrieve and parse the latest remote properties // Retrieve and parse the latest remote properties
BdfList message = BdfList message = clientHelper.getMessageAsList(txn,
clientHelper.getMessageAsList(txn, latest.messageId); latest.messageId, false);
remote = parseProperties(message); remote = parseProperties(message);
} }
// Merge in any discovered properties // Merge in any discovered properties
@@ -311,13 +311,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (latest == null) { if (latest == null) {
merged = new TransportProperties(p); merged = new TransportProperties(p);
Iterator<String> it = merged.values().iterator(); Iterator<String> it = merged.values().iterator();
//noinspection Java8CollectionRemoveIf
while (it.hasNext()) { while (it.hasNext()) {
if (isNullOrEmpty(it.next())) it.remove(); if (isNullOrEmpty(it.next())) it.remove();
} }
changed = true; changed = true;
} else { } else {
BdfList message = clientHelper.getMessageAsList(txn, BdfList message = clientHelper.getMessageAsList(txn,
latest.messageId); latest.messageId, false);
TransportProperties old = parseProperties(message); TransportProperties old = parseProperties(message);
merged = new TransportProperties(old); merged = new TransportProperties(old);
for (Entry<String, String> e : p.entrySet()) { for (Entry<String, String> e : p.entrySet()) {

View File

@@ -27,7 +27,10 @@ class TransportPropertyValidator extends BdfMessageValidator {
TransportPropertyValidator(ClientHelper clientHelper, TransportPropertyValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) { MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock); // Accept transport properties in non-canonical form
// TODO: Remove this after a reasonable migration period
// (added 2023-02-17)
super(clientHelper, metadataEncoder, clock, false);
} }
@Override @Override

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.record; package org.briarproject.bramble.record;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
@@ -45,7 +44,7 @@ class RecordReaderImpl implements RecordReader {
@Nullable @Nullable
@Override @Override
public Record readRecord(Predicate<Record> accept, Predicate<Record> ignore) public Record readRecord(RecordPredicate accept, RecordPredicate ignore)
throws IOException { throws IOException {
while (true) { while (true) {
if (eof()) return null; if (eof()) return null;

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
@@ -30,6 +30,7 @@ import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.PRIORITY; import static org.briarproject.bramble.api.sync.RecordTypes.PRIORITY;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST; import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS; import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PRIORITY_NONCE_BYTES; import static org.briarproject.bramble.api.sync.SyncConstants.PRIORITY_NONCE_BYTES;
@@ -40,12 +41,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
class SyncRecordReaderImpl implements SyncRecordReader { class SyncRecordReaderImpl implements SyncRecordReader {
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static final RecordPredicate ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static final RecordPredicate IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());
@@ -126,6 +127,8 @@ class SyncRecordReaderImpl implements SyncRecordReader {
byte[] payload = nextRecord.getPayload(); byte[] payload = nextRecord.getPayload();
if (payload.length <= MESSAGE_HEADER_LENGTH) if (payload.length <= MESSAGE_HEADER_LENGTH)
throw new FormatException(); throw new FormatException();
if (payload.length > MAX_MESSAGE_LENGTH)
throw new FormatException();
// Validate timestamp // Validate timestamp
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH); long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
if (timestamp < 0) throw new FormatException(); if (timestamp < 0) throw new FormatException();

View File

@@ -32,8 +32,7 @@ class SessionParserImpl implements SessionParser {
@Override @Override
public Session parseSession(BdfDictionary meta) throws FormatException { public Session parseSession(BdfDictionary meta) throws FormatException {
State state = State state = State.fromValue(meta.getInt(SESSION_KEY_STATE));
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
MessageId lastLocalMessageId = null; MessageId lastLocalMessageId = null;
byte[] lastLocalMessageIdBytes = byte[] lastLocalMessageIdBytes =
@@ -56,9 +55,9 @@ class SessionParserImpl implements SessionParser {
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP); Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
KeySetId keySetId = null; KeySetId keySetId = null;
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID); Integer keySetIdInt = meta.getOptionalInt(SESSION_KEY_KEY_SET_ID);
if (keySetIdLong != null) { if (keySetIdInt != null) {
keySetId = new KeySetId(keySetIdLong.intValue()); keySetId = new KeySetId(keySetIdInt);
} }
return new Session(state, lastLocalMessageId, localKeyPair, return new Session(state, lastLocalMessageId, localKeyPair,

View File

@@ -177,8 +177,8 @@ class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
protected DeliveryAction incomingMessage(Transaction txn, Message m, protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfList body, BdfDictionary meta) BdfList body, BdfDictionary meta)
throws DbException, FormatException { throws DbException, FormatException {
MessageType type = MessageType.fromValue( MessageType type =
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue()); MessageType.fromValue(meta.getInt(MSG_KEY_MESSAGE_TYPE));
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID)); TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Received " + type + " message for " + t); LOG.info("Received " + type + " message for " + t);

View File

@@ -42,7 +42,7 @@ class TransportKeyAgreementValidator extends BdfMessageValidator {
@Override @Override
protected BdfMessageContext validateMessage(Message m, Group g, protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException { BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getLong(0).intValue()); MessageType type = MessageType.fromValue(body.getInt(0));
if (type == KEY) return validateKeyMessage(m.getTimestamp(), body); if (type == KEY) return validateKeyMessage(m.getTimestamp(), body);
else if (type == ACTIVATE) return validateActivateMessage(body); else if (type == ACTIVATE) return validateActivateMessage(body);
else throw new AssertionError(); else throw new AssertionError();

View File

@@ -301,8 +301,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
BdfList cv = body.getList(i); BdfList cv = body.getList(i);
ClientId clientId = new ClientId(cv.getString(0)); ClientId clientId = new ClientId(cv.getString(0));
int majorVersion = cv.getLong(1).intValue(); int majorVersion = cv.getInt(1);
int minorVersion = cv.getLong(2).intValue(); int minorVersion = cv.getInt(2);
parsed.add(new ClientVersion(clientId, majorVersion, minorVersion)); parsed.add(new ClientVersion(clientId, majorVersion, minorVersion));
} }
return parsed; return parsed;
@@ -408,8 +408,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
throws FormatException { throws FormatException {
// Client ID, major version, minor version, active // Client ID, major version, minor version, active
ClientId clientId = new ClientId(clientState.getString(0)); ClientId clientId = new ClientId(clientState.getString(0));
int majorVersion = clientState.getLong(1).intValue(); int majorVersion = clientState.getInt(1);
int minorVersion = clientState.getLong(2).intValue(); int minorVersion = clientState.getInt(2);
boolean active = clientState.getBoolean(3); boolean active = clientState.getBoolean(3);
return new ClientState(clientId, majorVersion, minorVersion, active); return new ClientState(clientId, majorVersion, minorVersion, active);
} }

View File

@@ -43,9 +43,9 @@ class ClientVersioningValidator extends BdfMessageValidator {
checkSize(clientState, 4); checkSize(clientState, 4);
String clientId = clientState.getString(0); String clientId = clientState.getString(0);
checkLength(clientId, 1, MAX_CLIENT_ID_LENGTH); checkLength(clientId, 1, MAX_CLIENT_ID_LENGTH);
int majorVersion = clientState.getLong(1).intValue(); int majorVersion = clientState.getInt(1);
if (majorVersion < 0) throw new FormatException(); if (majorVersion < 0) throw new FormatException();
int minorVersion = clientState.getLong(2).intValue(); int minorVersion = clientState.getInt(2);
if (minorVersion < 0) throw new FormatException(); if (minorVersion < 0) throw new FormatException();
clientState.getBoolean(3); clientState.getBoolean(3);
} }

View File

@@ -1,36 +0,0 @@
d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 104.168.68.90:443 ED55B3C321E44EA7E50EF568C8A63CF75E89A58C cert=fgonxDvltTp8nmcOE9sUG94eOAALxETVVXAwnTZJLPpf7rjPuTp+abKl4VyFkxfcLRr5KQ iat-mode=0
n Bridge obfs4 45.142.181.131:42069 6EBCF6B02DA2B982F4080A7119D737366AFB74FA cert=9HyWH/BCwWzNirZdTQtluCgJk+gFhpOqydIuyQ1iDvpnqsAynKF+zcPE/INZFefm86UlBg iat-mode=0
n Bridge obfs4 85.214.28.204:47111 78A36E46BB082A471848239D3F4390A8F8C6084D cert=96sr3eaUFBDu4wFVAQIfNFElh0UNuutJ/3/Fh2Vu2PHfacQ8bwfY02bwG351U8BZpLnfUQ iat-mode=0
n Bridge obfs4 152.67.77.101:4096 B82DB9CDDF887AB8A859420E07DF298E30AF8A6E cert=21OWn3yFo+hulmQNAOtF5uwwOqWtdT5PrLhk8BG9DpOd0/k5DEkQEYPyDdXbS9nZ0E5BJA iat-mode=0
n Bridge obfs4 185.103.252.72:443 75F15E9339FF572F88F5588D429FEA379744BC53 cert=nOZ/SaRE3L1dChvjfe0Ks/wM/F8iFhwd3g2G5zgtcLB8x+wiZRWCwjRrbbiQyb3Gh2mxRQ iat-mode=0
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0
n Bridge obfs4 20.102.79.78:22022 B5705F7E616DAB0F477E3E1ADC23E40413F683FE cert=1Cc/hwPtPjzFKGHVOP0j/qmBgnvquRx8+im35/u5TIYjDQ3FlMfA5VvTrQ/hbX8BZZooLQ iat-mode=0
n Bridge obfs4 152.70.180.20:1993 3327C43587E66AD5F874C4234A1D72C938AD7318 cert=s7xLRUO2psaX7TMUP2YhXdxItR4U6K7D+E3gQaS/+yWUppevtazIibq4dN1g5Reu6dD2QQ iat-mode=0
n Bridge obfs4 144.202.12.254:10002 4E220F45CD404C8A3082A36326A5ED19BB8D4404 cert=iLz5YYWO4pUw7U7MRNOSvE0qO+IVeE4kVfFVWPO3coH3FmZtrkvlaTklfXxHZaCcXWBgaA iat-mode=0
n Bridge obfs4 109.14.168.159:5082 BFE1416DEFFE969581F016A4A319A87FFB26BA91 cert=n3X1CDdKBPXPIzfKh83p3ydfMzb0AD9gKC+/gIpHb7+xjjAnYO9x3LT+T/MvOIfAXxYySg iat-mode=0
n Bridge obfs4 45.150.172.16:80 82849E69CBB25EA7F479155F7DCD89D85717FF47 cert=+Krdu1jmVQOxWkMj0mqJHgwbQV49eyD6mZDS+mRExssWNHosa60g4P5Gp81sBJKzN8NrSw iat-mode=0
n Bridge obfs4 185.177.207.132:8443 4FB781F7A9DD39DA53A7996907817FC479874D19 cert=UL2gCAXWW5kEWY4TQ0lNeu6OAmzh40bXYVhMnTWVG8USnyy/zEKGSIPgmwTDMumWr9c1Pg iat-mode=0
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB
v Bridge 75.100.92.154:22815 465E990FA8A752DDE861EDF31E42454ACC037AB4
v Bridge 87.100.193.2:9010 13FB63452AADFA082BD2BC3E1E320AD301F07877
v Bridge 65.21.240.163:33245 20BD59649212CFE7412BFC9B94C3CCCFD8F807A8
m Bridge meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com utls=hellochrome_auto
s Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72

View File

@@ -1,4 +0,0 @@
ZZ 1 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
ZZ 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:stun.l.google.com:19302,stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellochrome_auto
TM 1 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto
TM 0 url=https://snowflake-broker.azureedge.net/ front=ajax.aspnetcdn.com ice=stun:206.53.159.130:3479,stun:176.119.42.11:3479,stun:94.23.17.185:3479,stun:217.74.179.29:3479,stun:83.125.8.47:3479,stun:23.253.102.137:3479,stun:52.26.251.34:3479,stun:52.26.251.34:3479,stun:18.191.223.12:3479,stun:154.73.34.8:3479,stun:185.125.180.70:3479,stun:195.35.115.37:3479 utls-imitate=hellochrome_auto

View File

@@ -56,7 +56,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(timestamp - MAX_CLOCK_DIFFERENCE)); will(returnValue(timestamp - MAX_CLOCK_DIFFERENCE));
oneOf(clientHelper).toList(message.getBody()); oneOf(clientHelper).toList(message, true);
will(returnValue(body)); will(returnValue(body));
oneOf(metadataEncoder).encode(dictionary); oneOf(metadataEncoder).encode(dictionary);
will(returnValue(meta)); will(returnValue(meta));
@@ -86,7 +86,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(timestamp)); will(returnValue(timestamp));
oneOf(clientHelper).toList(shortMessage.getBody()); oneOf(clientHelper).toList(shortMessage, true);
will(returnValue(body)); will(returnValue(body));
oneOf(metadataEncoder).encode(dictionary); oneOf(metadataEncoder).encode(dictionary);
will(returnValue(meta)); will(returnValue(meta));
@@ -114,7 +114,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(timestamp)); will(returnValue(timestamp));
oneOf(clientHelper).toList(message.getBody()); oneOf(clientHelper).toList(message, true);
will(throwException(new FormatException())); will(throwException(new FormatException()));
}}); }});
@@ -126,7 +126,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(timestamp)); will(returnValue(timestamp));
oneOf(clientHelper).toList(message.getBody()); oneOf(clientHelper).toList(message, true);
will(returnValue(body)); will(returnValue(body));
}}); }});

View File

@@ -546,7 +546,7 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory) oneOf(bdfReaderFactory)
.createReader(with(any(InputStream.class))); .createReader(with(any(InputStream.class)), with(true));
will(returnValue(bdfReader)); will(returnValue(bdfReader));
oneOf(bdfReader).readList(); oneOf(bdfReader).readList();
will(returnValue(list)); will(returnValue(list));

View File

@@ -0,0 +1,316 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReader.RecordPredicate;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MAJOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_EPHEMERAL_PUBLIC_KEY;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_MINOR_VERSION;
import static org.briarproject.bramble.contact.HandshakeRecordTypes.RECORD_TYPE_PROOF_OF_OWNERSHIP;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getPendingContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class HandshakeManagerImplTest extends BrambleMockTestCase {
private final TransactionManager db =
context.mock(TransactionManager.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ContactManager contactManager =
context.mock(ContactManager.class);
private final TransportCrypto transportCrypto =
context.mock(TransportCrypto.class);
private final HandshakeCrypto handshakeCrypto =
context.mock(HandshakeCrypto.class);
private final RecordReaderFactory recordReaderFactory =
context.mock(RecordReaderFactory.class);
private final RecordWriterFactory recordWriterFactory =
context.mock(RecordWriterFactory.class);
private final RecordReader recordReader = context.mock(RecordReader.class);
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
private final PendingContact pendingContact = getPendingContact();
private final PublicKey theirStaticPublicKey =
pendingContact.getPublicKey();
private final PublicKey ourStaticPublicKey = getAgreementPublicKey();
private final PrivateKey ourStaticPrivateKey = getAgreementPrivateKey();
private final KeyPair ourStaticKeyPair =
new KeyPair(ourStaticPublicKey, ourStaticPrivateKey);
private final PublicKey theirEphemeralPublicKey = getAgreementPublicKey();
private final PublicKey ourEphemeralPublicKey = getAgreementPublicKey();
private final PrivateKey ourEphemeralPrivateKey = getAgreementPrivateKey();
private final KeyPair ourEphemeralKeyPair =
new KeyPair(ourEphemeralPublicKey, ourEphemeralPrivateKey);
private final SecretKey masterKey = getSecretKey();
private final byte[] ourProof = getRandomBytes(PROOF_BYTES);
private final byte[] theirProof = getRandomBytes(PROOF_BYTES);
private final InputStream in = new ByteArrayInputStream(new byte[0]);
private final OutputStream out = new ByteArrayOutputStream(0);
private final HandshakeManagerImpl handshakeManager =
new HandshakeManagerImpl(db, identityManager, contactManager,
transportCrypto, handshakeCrypto, recordReaderFactory,
recordWriterFactory);
@Test
public void testHandshakeAsAliceWithPeerVersion_0_1() throws Exception {
testHandshakeWithPeerVersion_0_1(true);
}
@Test
public void testHandshakeAsBobWithPeerVersion_0_1() throws Exception {
testHandshakeWithPeerVersion_0_1(false);
}
private void testHandshakeWithPeerVersion_0_1(boolean alice)
throws Exception {
expectPrepareForHandshake(alice);
expectSendMinorVersion();
expectSendKey();
// Remote peer sends minor version, so use new key derivation
expectReceiveMinorVersion();
expectReceiveKey();
expectDeriveMasterKey_0_1(alice);
expectDeriveProof(alice);
expectSendProof();
expectReceiveProof();
expectSendEof();
expectReceiveEof();
expectVerifyOwnership(alice, true);
HandshakeResult result = handshakeManager.handshake(
pendingContact.getId(), in, streamWriter);
assertArrayEquals(masterKey.getBytes(),
result.getMasterKey().getBytes());
assertEquals(alice, result.isAlice());
}
@Test
public void testHandshakeAsAliceWithPeerVersion_0_0() throws Exception {
testHandshakeWithPeerVersion_0_0(true);
}
@Test
public void testHandshakeAsBobWithPeerVersion_0_0() throws Exception {
testHandshakeWithPeerVersion_0_0(false);
}
private void testHandshakeWithPeerVersion_0_0(boolean alice)
throws Exception {
expectPrepareForHandshake(alice);
expectSendMinorVersion();
expectSendKey();
// Remote peer does not send minor version, so use old key derivation
expectReceiveKey();
expectDeriveMasterKey_0_0(alice);
expectDeriveProof(alice);
expectSendProof();
expectReceiveProof();
expectSendEof();
expectReceiveEof();
expectVerifyOwnership(alice, true);
HandshakeResult result = handshakeManager.handshake(
pendingContact.getId(), in, streamWriter);
assertArrayEquals(masterKey.getBytes(),
result.getMasterKey().getBytes());
assertEquals(alice, result.isAlice());
}
@Test(expected = FormatException.class)
public void testProofOfOwnershipNotVerifiedAsAlice() throws Exception {
testProofOfOwnershipNotVerified(true);
}
@Test(expected = FormatException.class)
public void testProofOfOwnershipNotVerifiedAsBob() throws Exception {
testProofOfOwnershipNotVerified(false);
}
private void testProofOfOwnershipNotVerified(boolean alice)
throws Exception {
expectPrepareForHandshake(alice);
expectSendMinorVersion();
expectSendKey();
expectReceiveMinorVersion();
expectReceiveKey();
expectDeriveMasterKey_0_1(alice);
expectDeriveProof(alice);
expectSendProof();
expectReceiveProof();
expectSendEof();
expectReceiveEof();
expectVerifyOwnership(alice, false);
handshakeManager.handshake(pendingContact.getId(), in, streamWriter);
}
private void expectPrepareForHandshake(boolean alice) throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(contactManager).getPendingContact(txn,
pendingContact.getId());
will(returnValue(pendingContact));
oneOf(identityManager).getHandshakeKeys(txn);
will(returnValue(ourStaticKeyPair));
oneOf(transportCrypto).isAlice(theirStaticPublicKey,
ourStaticKeyPair);
will(returnValue(alice));
oneOf(recordReaderFactory).createRecordReader(in);
will(returnValue(recordReader));
oneOf(streamWriter).getOutputStream();
will(returnValue(out));
oneOf(recordWriterFactory).createRecordWriter(out);
will(returnValue(recordWriter));
oneOf(handshakeCrypto).generateEphemeralKeyPair();
will(returnValue(ourEphemeralKeyPair));
}});
}
private void expectSendMinorVersion() throws Exception {
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_MINOR_VERSION,
new byte[] {PROTOCOL_MINOR_VERSION}));
}
private void expectReceiveMinorVersion() throws Exception {
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_MINOR_VERSION,
new byte[] {PROTOCOL_MINOR_VERSION}));
}
private void expectSendKey() throws Exception {
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
ourEphemeralPublicKey.getEncoded()));
}
private void expectReceiveKey() throws Exception {
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_EPHEMERAL_PUBLIC_KEY,
theirEphemeralPublicKey.getEncoded()));
}
private void expectDeriveMasterKey_0_1(boolean alice) throws Exception {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).deriveMasterKey_0_1(theirStaticPublicKey,
theirEphemeralPublicKey, ourStaticKeyPair,
ourEphemeralKeyPair, alice);
will(returnValue(masterKey));
}});
}
private void expectDeriveMasterKey_0_0(boolean alice) throws Exception {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).deriveMasterKey_0_0(theirStaticPublicKey,
theirEphemeralPublicKey, ourStaticKeyPair,
ourEphemeralKeyPair, alice);
will(returnValue(masterKey));
}});
}
private void expectDeriveProof(boolean alice) {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).proveOwnership(masterKey, alice);
will(returnValue(ourProof));
}});
}
private void expectSendProof() throws Exception {
expectWriteRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_PROOF_OF_OWNERSHIP, ourProof));
}
private void expectReceiveProof() throws Exception {
expectReadRecord(new Record(PROTOCOL_MAJOR_VERSION,
RECORD_TYPE_PROOF_OF_OWNERSHIP, theirProof));
}
private void expectSendEof() throws Exception {
context.checking(new Expectations() {{
oneOf(streamWriter).sendEndOfStream();
}});
}
private void expectReceiveEof() throws Exception {
context.checking(new Expectations() {{
oneOf(recordReader).readRecord(with(any(RecordPredicate.class)),
with(any(RecordPredicate.class)));
will(returnValue(null));
}});
}
private void expectVerifyOwnership(boolean alice, boolean verified) {
context.checking(new Expectations() {{
oneOf(handshakeCrypto).verifyOwnership(masterKey, !alice,
theirProof);
will(returnValue(verified));
}});
}
private void expectWriteRecord(Record record) throws Exception {
context.checking(new Expectations() {{
oneOf(recordWriter).writeRecord(with(new PredicateMatcher<>(
Record.class, r -> recordEquals(record, r))));
oneOf(recordWriter).flush();
}});
}
private boolean recordEquals(Record expected, Record actual) {
return expected.getProtocolVersion() == actual.getProtocolVersion() &&
expected.getRecordType() == actual.getRecordType() &&
Arrays.equals(expected.getPayload(), actual.getPayload());
}
private void expectReadRecord(Record record) throws Exception {
context.checking(new Expectations() {{
// Test that the `accept` predicate passed to the reader would
// accept the expected record
oneOf(recordReader).readRecord(with(new PredicateMatcher<>(
RecordPredicate.class, rp -> rp.test(record))),
with(any(RecordPredicate.class)));
will(returnValue(record));
}});
}
}

View File

@@ -13,6 +13,7 @@ import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Locale;
import static java.lang.System.arraycopy; import static java.lang.System.arraycopy;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
@@ -174,7 +175,7 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
rawLink[0] = (byte) formatVersion; rawLink[0] = (byte) formatVersion;
byte[] publicKeyBytes = publicKey.getEncoded(); byte[] publicKeyBytes = publicKey.getEncoded();
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length); arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
String base32 = Base32.encode(rawLink).toLowerCase(); String base32 = Base32.encode(rawLink).toLowerCase(Locale.US);
assertEquals(BASE32_LINK_BYTES, base32.length()); assertEquals(BASE32_LINK_BYTES, base32.length());
return base32; return base32;
} }

Some files were not shown because too many files have changed in this diff Show More