Compare commits

..

313 Commits

Author SHA1 Message Date
akwizgran 2962afa6f1 Restrict getMessage() method to small messages. 2020-11-03 17:19:39 +00:00
akwizgran 4490a2cd3f Add database methods for selecting small messages. 2020-11-03 16:57:50 +00:00
akwizgran e2dbc92083 Log migration times. 2020-11-03 16:56:37 +00:00
akwizgran 4fd3970b4f Create blocks table. 2020-11-03 16:55:38 +00:00
akwizgran 46da4aa59e Close statements after running migrations. 2020-11-03 16:51:36 +00:00
Torsten Grote 64e1975cf1 Merge branch 'adaptive-icon' into 'master'
Add adaptive icon for API 26+ and Play Store icon

Closes #1456

See merge request briar/briar!1293
2020-11-03 11:55:12 +00:00
akwizgran 993502add0 Add adaptive icon for API 26+ and Play Store icon. 2020-11-03 11:35:53 +00:00
akwizgran 54893d2716 Bump version numbers for 1.2.12 release. 2020-11-02 14:51:34 +00:00
akwizgran 84657127b8 Update translations. 2020-11-02 14:50:06 +00:00
akwizgran 01a146ba71 Merge branch '1647-illegal-state' into 'master'
Fix IllegalStateException when creating image attachments

Closes #1647

See merge request briar/briar!1187
2020-10-30 16:17:20 +00:00
akwizgran a30e5b672e Merge branch '1592-image-placeholders' into 'master'
Show Attachment Placeholders

Closes #1592

See merge request briar/briar!1186
2020-10-30 15:54:25 +00:00
Torsten Grote edb584dc3b Merge branch 'add-contacts-via-bluetooth' into 'master'
Add contacts via Bluetooth if possible

See merge request briar/briar!1292
2020-10-29 16:54:05 +00:00
akwizgran 12a8907c8b Ignore missing location permission on API < 23 where it's not needed. 2020-10-29 14:34:10 +00:00
akwizgran e0f381a973 Try all transports in order of preference. 2020-10-29 11:48:10 +00:00
Torsten Grote 61d3d133e8 Merge branch '1147-only-alice-performs-discovery' into 'master'
Only Alice should perform Bluetooth discovery

See merge request briar/briar!1291
2020-10-28 11:11:34 +00:00
akwizgran 0caa522f07 Remove error message, return to intro fragment when retrying. 2020-10-27 17:37:22 +00:00
akwizgran 948212103c Require Bluetooth permissions if device supports Bluetooth. 2020-10-27 16:24:34 +00:00
akwizgran ce1a57c2b4 Prefer Bluetooth for adding contacts. 2020-10-27 16:24:33 +00:00
akwizgran 922a52bf83 Only Alice should perform Bluetooth discovery. 2020-10-27 16:21:30 +00:00
akwizgran 8cbb38ee68 Bump version numbers for 1.2.11 release. 2020-10-14 13:15:29 +01:00
akwizgran 1c4cf7d771 Update translations. 2020-10-14 13:14:05 +01:00
akwizgran 090a1bd84e Merge branch '1781-change-alias' into 'master'
Add method to change contact alias to REST API

Closes #1781

See merge request briar/briar!1286
2020-10-14 11:47:10 +00:00
Nico Alt 44f6f5d416 Add method to change contact alias to REST API
Needed for https://code.briarproject.org/briar/briar-gtk/-/issues/14 and
https://code.briarproject.org/briar/python-briar-wrapper/-/issues/6.

Fixes #1781
2020-10-13 23:33:26 +02:00
Torsten Grote b88f012880 Merge branch 'make-crash-report-text-selectable' into 'master'
Make the text of crash reports selectable

See merge request briar/briar!1290
2020-10-13 16:23:49 +00:00
akwizgran 93f434e54b Merge branch '1782-delete-all-messages' into 'master'
Add method to delete all private messages to REST API

Closes #1782

See merge request briar/briar!1287
2020-10-13 15:46:01 +00:00
akwizgran 92f4a3a404 Make the text of crash reports selectable.
This makes it possible for users to send device data by other means if
they can't connect to Tor to send a crash report.
2020-10-13 16:44:43 +01:00
Nico Alt c017a813b0 Add output of DeletionResult to deleteAllMessages call 2020-10-08 15:03:49 +02:00
Nico Alt 6c6dbfd357 Add method to delete all private messages to REST API
Needed for https://code.briarproject.org/briar/briar-gtk/-/issues/11.

Fixes #1782
2020-10-08 14:03:16 +02:00
akwizgran 1f246637e2 Merge branch 'kotlin-no-star-imports' into 'master'
Change Kotlin coding style to not do star imports

See merge request briar/briar!1289
2020-10-05 13:54:51 +00:00
Torsten Grote 1ac17cf859 [headless] Change coding style to not do star imports 2020-10-05 09:54:35 -03:00
akwizgran 0a3ff41feb Merge branch '1780-mark-as-read' into 'master'
Add method to mark message as read to REST API

Closes #1780

See merge request briar/briar!1285
2020-10-05 11:38:33 +00:00
Nico Alt 9738dd2838 Add method to mark message as read to REST API
When exposing unread messages counters in
https://code.briarproject.org/briar/briar/-/merge_requests/1283, I
noticed that they were never set to 0.

Fixes #1780
2020-10-03 23:23:54 +02:00
akwizgran be0e21d39b Merge branch '1507-extract-tor-binaries-to-lib-dir' into 'master'
Raise targetSdkVersion to 29, package Tor binaries as libraries

Closes #1507 and #1185

See merge request briar/briar!1279
2020-09-29 13:19:42 +00:00
Torsten Grote 6a2c2bed0f Merge branch '1785-bluetooth-adapter-npe' into 'master'
Check whether Bluetooth adapter exists before trying to get address

Closes #1785

See merge request briar/briar!1288
2020-09-29 12:51:22 +00:00
akwizgran de9c6d4447 Extract version constants into top-level build file. 2020-09-29 13:50:17 +01:00
akwizgran 37a2d9f990 Extract binaries even if older versions already exist. 2020-09-29 13:48:45 +01:00
akwizgran 0e1fb406b5 Extract library filenames into constants. 2020-09-29 13:48:45 +01:00
akwizgran b72e8fa490 Package Tor binaries as libraries so we're allowed to execute them. 2020-09-29 13:48:45 +01:00
akwizgran f3157e5276 Raise target SDK version to 29. 2020-09-29 13:48:43 +01:00
akwizgran e2124ff3c9 Merge branch '1779-headless-messages-sent-acked' into 'master'
Expose message delivery state changes to websockets API

Closes #1779

See merge request briar/briar!1284
2020-09-29 12:46:41 +00:00
akwizgran 66cc9d25e7 Merge branch '1746-headless-unread-counter' into 'master'
Expose unread messages count in API's contacts list

Closes #1746

See merge request briar/briar!1283
2020-09-29 12:45:30 +00:00
akwizgran e9cdec95e0 Check whether Bluetooth adapter exists before trying to get address. 2020-09-29 13:39:46 +01:00
Nico Alt 63d3a78dda Expose message delivery state changes to websockets API
We already indicate whether a message was sent/acked, but we don't
inform about updates.

Needed for briar-gtk#69.

Fixes #1779
2020-09-25 22:39:40 +02:00
Nico Alt ccbe6d4bb8 Expose unread messages count in API's contacts list
Fixes #1746
2020-09-25 17:46:55 +02:00
akwizgran 54b852db70 Bump version numbers for 1.2.10 release. 2020-09-25 13:42:28 +01:00
akwizgran 8d55ea3f6f Update translations. 2020-09-25 13:41:31 +01:00
Torsten Grote 4e5f2e31df Merge branch 'deterministic-briar-headless-jar' into 'master'
Make briar-headless.jar deterministic

See merge request briar/briar!1282
2020-09-17 20:25:12 +00:00
akwizgran 518c0370c8 Make briar-headless.jar deterministic. 2020-09-17 16:13:01 +01:00
akwizgran 7ef2fb5f0c Update Dutch translation. 2020-09-17 14:55:11 +01:00
akwizgran 1210b27bd1 Update translations. 2020-09-17 14:48:10 +01:00
Torsten Grote cdf1a4abcd Merge branch 'update-feed-manager-integration-test-expectations' into 'master'
Update FeedManagerIntegrationTest expectations

See merge request briar/briar!1281
2020-09-10 15:05:39 +00:00
akwizgran b18ef7e72d Update FeedManagerIntegrationTest expectations.
The "Schneier on Security" RSS feed no longer has a description.
2020-09-10 15:56:26 +01:00
Torsten Grote 48d907dda5 Merge branch '185-transports-activity' into 'master'
Add connections screen with information about transports

Closes #185

See merge request briar/briar!1277
2020-09-04 12:27:52 +00:00
akwizgran 3e5b7f451a Merge branch '1716-duplicate-unlock-screen' into 'master'
Don't show duplicate unlock screen on API 29+

Closes #1716

See merge request briar/briar!1280
2020-09-04 12:07:46 +00:00
akwizgran 95cccd1d15 Don't show duplicate unlock screen on API 29+. 2020-09-04 12:37:00 +01:00
Torsten Grote 0a33c77393 Merge branch 'cancel-rendezvous-polling' into 'master'
Only run the rendezvous polling task when we have pending contacts

See merge request briar/briar!1276
2020-09-01 11:53:16 +00:00
Torsten Grote 80caa7634a Merge branch 'do-not-enable-or-disable-bluetooth-automatically' into 'master'
Don't enable or disable the Bluetooth adapter automatically

Closes #1348

See merge request briar/briar!1278
2020-08-14 17:17:22 +00:00
akwizgran 2a8778d3cc Don't enable or disable the Bluetooth adapter automatically. 2020-08-14 16:18:02 +01:00
akwizgran 2cf146a104 Initialise Bluetooth state when view model is created. 2020-08-14 16:13:29 +01:00
akwizgran a1e3c81bda Remove unused drawable. 2020-08-14 15:45:34 +01:00
akwizgran bbcb183c24 Use a single click target that covers all transport indicators. 2020-08-14 15:25:52 +01:00
akwizgran 7fcb3394ca Add optional summary text to transport cards. 2020-08-14 15:25:51 +01:00
akwizgran 4310e4d1af Add help button to transports activity. 2020-08-14 15:25:51 +01:00
akwizgran 82e85bdb39 Remove redundant separator. 2020-08-14 15:25:51 +01:00
akwizgran 5ba0728abc Add onboarding for transports activity. 2020-08-14 15:25:51 +01:00
akwizgran 46bdb3589c Use Briar card style (sets background colour for dark theme). 2020-08-14 15:25:51 +01:00
akwizgran 392bc0d339 Use resource for title of transports activity. 2020-08-14 15:25:51 +01:00
akwizgran 02cf6bfcaa Use constants for default settings. 2020-08-14 15:25:51 +01:00
akwizgran 08a8a0b281 Show reason why Tor is disabled. 2020-08-14 15:25:51 +01:00
akwizgran b189a38f62 Only show plugin status when it's relevant. 2020-08-14 15:25:50 +01:00
akwizgran 57b0641e5f Update network status. 2020-08-14 15:25:50 +01:00
akwizgran 5b5d513316 Shorter explanations. 2020-08-14 15:25:50 +01:00
akwizgran 6684fb2e1b Add settings button to toolbar. 2020-08-14 15:25:50 +01:00
akwizgran 73c6a29ede Add transports activity. 2020-08-14 15:25:50 +01:00
akwizgran a8fe0a01ac Only run the rendezvous polling task when we have pending contacts. 2020-08-14 14:49:04 +01:00
Torsten Grote c75c8da4b9 Merge branch '1759-fix-periodic-task-cancellation' into 'master'
Fix cancellation of periodic tasks, remove ticker

Closes #1759

See merge request briar/briar!1274
2020-08-14 12:47:20 +00:00
Torsten Grote 2f3f3d256c Merge branch '1745-try-to-bind-on-connectivity-change' into 'master'
Try to bind IPv4 and IPv6 sockets whenever connectivity changes

Closes #1745

See merge request briar/briar!1275
2020-08-14 11:24:18 +00:00
Torsten Grote 1141d01dc7 Merge branch '1758-release-timeout-monitor-resources' into 'master'
Ensure TimeoutInputStreams are closed

Closes #1758

See merge request briar/briar!1273
2020-08-14 11:00:49 +00:00
akwizgran e06eee2358 Try to bind IPv4 and IPv6 sockets whenever connectivity changes. 2020-08-14 10:44:33 +01:00
akwizgran c37fe2a246 Fix cancellation of periodic tasks, remove ticker. 2020-08-13 16:35:32 +01:00
akwizgran 79ad5ca07e Ensure TimeoutInputStreams are closed. 2020-08-13 16:35:03 +01:00
akwizgran 0e2d905486 Merge branch '1142-wakeful-lifecycle' into 'master'
Hold a wake lock during app startup and shutdown

See merge request briar/briar!1271
2020-08-13 15:33:09 +00:00
akwizgran 6094014487 Add a comment discouraging use of dedicated threads. 2020-08-13 16:23:44 +01:00
akwizgran 9603ff93e9 Don't sign out when panic button is triggered if configured not to. 2020-08-13 10:19:33 +01:00
akwizgran e7ac6aef8c Improve readability. 2020-08-13 10:14:13 +01:00
Torsten Grote 4e18115d88 Merge branch '1721-fine-location' into 'master'
Request fine location permission for Bluetooth discovery

Closes #1721

See merge request briar/briar!1272
2020-08-12 19:40:48 +00:00
akwizgran b57fb9c842 Request fine location permission for Bluetooth discovery. 2020-08-12 17:29:47 +01:00
akwizgran 196a2b7e22 Fix constructor arguments in unit test. 2020-08-12 17:19:13 +01:00
akwizgran 37712203d7 Hold a wake lock while signing out. 2020-08-12 16:47:02 +01:00
akwizgran cc67237893 Don't redundantly use wakeful IO executor. 2020-08-12 16:47:02 +01:00
akwizgran 79f3a77e1a Annotate methods that should be called with a wake lock. 2020-08-12 16:47:02 +01:00
akwizgran 3ecec61c25 Hold a wake lock while starting plugins. 2020-08-12 16:47:02 +01:00
akwizgran 1e2dc862ef Hold a wake lock during app startup and shutdown. 2020-08-12 16:47:02 +01:00
akwizgran 452c3afbb3 Merge branch '1142-inject-plugin-factories' into 'master'
Use injection to create plugin factories

See merge request briar/briar!1270
2020-08-12 15:46:02 +00:00
akwizgran 9d60fbe957 Merge branch '1142-wakeful-polling' into 'master'
Hold a wake lock while polling

See merge request briar/briar!1269
2020-08-12 15:45:08 +00:00
akwizgran 434b8a37f3 Use wakeful IO executor for polling, reconnection tasks. 2020-08-10 17:24:04 +01:00
akwizgran 5e6a382b4b Merge branch '1142-wakeful-tasks' into 'master'
Hold a wake lock while running scheduled tasks

See merge request briar/briar!1268
2020-08-10 12:54:00 +00:00
akwizgran b5bb4aff7f Merge branch 'master' into '1142-wakeful-tasks'
# Conflicts:
#   bramble-android/src/main/java/org/briarproject/bramble/system/AndroidTaskScheduler.java
2020-08-10 12:37:50 +00:00
akwizgran b0bf9d5a8c Merge branch '1142-wake-lock-refactoring' into 'master'
Share a single wake lock among all holders

See merge request briar/briar!1267
2020-08-10 12:36:06 +00:00
akwizgran 1e6fd8bb74 Merge branch '1142-scheduler-interface' into 'master'
Use TaskScheduler for scheduling tasks

See merge request briar/briar!1266
2020-08-10 10:36:56 +00:00
akwizgran eac93f43d3 Add comments for wake lock handling. 2020-08-10 11:36:05 +01:00
akwizgran 23f22af6e4 Add a comment, wrap logging in an if. 2020-08-10 11:26:29 +01:00
akwizgran 6e8e955dc2 Update javadocs. 2020-08-07 16:16:31 +01:00
akwizgran 2e2bc2d82f Remove @Immutable annotation, which is no longer true. 2020-08-07 15:51:15 +01:00
akwizgran 1af951f8b4 Use injection to create plugin factories. 2020-08-07 15:11:03 +01:00
akwizgran 086c10abc0 Hold wake lock while polling. 2020-08-07 15:10:18 +01:00
akwizgran b5341700be Add wakeful IO executor. 2020-08-07 15:10:18 +01:00
akwizgran d8be340120 Use a unique log tag for each wake lock instance. 2020-08-07 15:09:14 +01:00
akwizgran 7e0d21de38 Add tags for wake lock holders. 2020-08-07 15:09:13 +01:00
akwizgran 1bab15baaf Add fine logging for wake locks. 2020-08-07 15:09:13 +01:00
akwizgran af1a91c819 Fix circular dependency between scheduler and wake lock manager. 2020-08-07 15:09:13 +01:00
akwizgran e6c3f82fe2 Fix test expectations. 2020-08-07 15:09:13 +01:00
akwizgran b2840c1b00 Add method for executing a task on an executor wakefully. 2020-08-07 15:09:13 +01:00
akwizgran 942bb28701 Hold a wake lock while running due tasks. 2020-08-07 15:09:11 +01:00
akwizgran 94dd0a2661 Hold a wake lock while scheduled tasks are running. 2020-08-07 15:08:53 +01:00
akwizgran 3aa00ecb3d Pass executor to scheduler. 2020-08-07 15:08:53 +01:00
akwizgran d5395d3d01 Shared wake lock must be a singleton. 2020-08-07 15:07:48 +01:00
akwizgran b6b721e3b1 Use a wider safety margin to allow for scheduler delays. 2020-08-07 15:02:50 +01:00
akwizgran 7cdd05fd67 Log a warning if the wake lock expires before it's renewed. 2020-08-07 15:02:49 +01:00
akwizgran 286f6f492c Share a single OS wake lock among all holders. 2020-08-07 15:02:49 +01:00
akwizgran eb6b4aa850 Move wake lock properties into factory. 2020-08-07 15:02:49 +01:00
akwizgran adb657a5b6 Ensure we only call openOutputStream() once. 2020-08-07 15:02:49 +01:00
akwizgran d794777eb2 Refactor wake lock creation. 2020-08-07 15:02:49 +01:00
akwizgran 090123579d Use elapsed time since boot rather than wall clock time. 2020-08-07 15:02:05 +01:00
akwizgran f1bde4e75c Fixed delay is easier to reconcile with sleeps than fixed rate. 2020-08-07 15:02:05 +01:00
akwizgran ac80a90ef3 Add Android task scheduler. 2020-08-07 15:02:05 +01:00
akwizgran dfefb88b32 Allow Android and headless to use different task schedulers. 2020-08-07 15:02:05 +01:00
akwizgran 86641741a0 Wrap scheduler in an interface. 2020-08-07 15:02:05 +01:00
Torsten Grote 280f87065e Merge branch 'reflected-bt-address' into 'master'
Use reflected BT address if we don't know our own address

See merge request briar/briar!1265
2020-08-07 13:59:00 +00:00
akwizgran cbe645a4a3 Merge branch 'reflected-transport-properties' into 'master'
Reflect discovered transport properties back to the remote peer

See merge request briar/briar!1264
2020-08-07 13:46:52 +00:00
akwizgran f4e9e10245 Remove unused constants. 2020-07-21 17:07:37 +01:00
akwizgran e9f78bc486 Avoid redundantly storing unchanged properties and settings. 2020-07-21 17:06:02 +01:00
akwizgran a4091be6f7 Ignore reflected address until we've made a Bluetooth connection.
This reduces the opportunities for contacts to reflect a false address.
2020-07-21 17:06:02 +01:00
akwizgran 49f0640278 Use reflected Bluetooth address if we don't know our own address. 2020-07-21 17:06:02 +01:00
akwizgran d617e67006 Add method for plugins to get remote properties. 2020-07-21 17:06:02 +01:00
akwizgran 2063f6c57c Don't attach contact ID to RemoteTransportPropertiesUpdatedEvent. 2020-07-21 17:05:41 +01:00
akwizgran f68d8d284f Merge branch 'master' into 'reflected-transport-properties'
# Conflicts:
#   bramble-core/src/main/java/org/briarproject/bramble/properties/TransportPropertyManagerImpl.java
2020-07-21 09:18:29 +00:00
Torsten Grote 3c63fecb5d Merge branch 'v3-hidden-service-migration' into 'master'
Use v3 hidden services (alongside v2 if a service already exists)

See merge request briar/briar!1263
2020-07-20 16:55:08 +00:00
akwizgran 41fdd584ad Test whether event is attached to transaction. 2020-07-17 17:37:22 +01:00
akwizgran 1b37dceb28 Only add the onion hostname to transport properties once. 2020-07-17 16:55:30 +01:00
akwizgran c183ca0340 Fix a lint warning. 2020-07-17 16:53:26 +01:00
akwizgran 90e91221d9 Reflect discovered properties back to the remote peer. 2020-07-16 14:25:43 +01:00
akwizgran b91fe66461 Broadcast an event when remote transport properties are updated. 2020-07-16 11:38:55 +01:00
akwizgran b5ab077afa Merge branch 'htmlcompat' into 'master'
Replace deprecated Html#fromHtml with HtmlCompat#fromHtml.

Closes #1435

See merge request briar/briar!1262
2020-07-14 09:39:21 +00:00
Tobias Preuss 496d4188c7 Replace deprecated Html#fromHtml with HtmlCompat#fromHtml.
Resolves #1435.
2020-07-13 19:52:22 +02:00
Torsten Grote ab682c82a3 Merge branch 'translation-md' into 'master'
Add TRANSLATION.md file

See merge request briar/briar!1261
2020-07-10 13:31:24 +00:00
akwizgran 375a7276ad Add link to LocLab wiki. 2020-07-10 14:22:06 +01:00
akwizgran b7084b2486 Add TRANSLATION.md file.
This was recommended by Translate House.
2020-07-10 11:19:57 +01:00
akwizgran aa152a80d1 Merge branch 'headless-connected' into 'master'
Expose contact connected state to REST API

See merge request briar/briar!1260
2020-07-06 14:50:57 +00:00
Torsten Grote 3f0d9233d9 [headless] expose contact connected state to REST API 2020-07-06 08:21:07 -03:00
akwizgran 9d96ce6db0 Bump version numbers for 1.2.9 release. 2020-07-04 22:40:58 +01:00
akwizgran 45fb5bb445 Merge branch 'expiry-overflow-bug' into 'master'
Fix overflow in expiry warning code, bypass expiry code in release builds

See merge request briar/briar!1259
2020-07-04 21:38:22 +00:00
akwizgran 0756d92ca1 Fix overflow in expiry warning code, bypass code in release builds. 2020-07-04 22:23:06 +01:00
akwizgran 37f80c7682 Bump version numbers for 1.2.8 release. 2020-07-03 16:03:50 +01:00
akwizgran b409215c57 Update translations. 2020-07-03 15:57:47 +01:00
Torsten Grote 4f0aaf03fd Merge branch '1744-check-socket-is-bound' into 'master'
Check server socket is bound

Closes #1744

See merge request briar/briar!1258
2020-07-03 14:51:05 +00:00
akwizgran 597fef6d50 Check server socket is bound. 2020-07-03 13:04:26 +01:00
akwizgran c1d0936a1e Log HS version when descriptor is uploaded. 2020-06-30 14:18:55 +01:00
akwizgran 717be0178a Allow local transport properties to be removed by setting empty values. 2020-06-30 14:05:46 +01:00
akwizgran 34677eb3a7 Migrate to v3 hidden services. 2020-06-30 12:22:16 +01:00
akwizgran 1dd15567de Merge branch 'style-guide-colours' into 'master'
Use colours from style guide

See merge request briar/briar!1257
2020-06-29 17:09:41 +00:00
akwizgran 428f06abdd Use style guide colours in drawables. 2020-06-29 17:58:10 +01:00
Torsten Grote e1d1c62708 Merge branch 'default-plugin-settings' into 'master'
Enable LAN plugin by default, move default settings to constants

See merge request briar/briar!1256
2020-06-29 16:52:51 +00:00
akwizgran ae75090d23 Use colours from style guide. 2020-06-29 17:20:42 +01:00
Torsten Grote 443043ae09 Merge branch 'plugin-toggles' into 'master'
Merge plugin-toggles feature branch

See merge request briar/briar!1255
2020-06-29 13:07:52 +00:00
akwizgran fb85730b8e Enable LAN by default, as on master.
Let's not break too many things at once.
2020-06-26 17:42:05 +01:00
akwizgran 48b1e77065 Use constants for default plugin settings. 2020-06-26 17:42:05 +01:00
akwizgran a03953563f Show appropriate text for answered introduction requests. 2020-06-26 17:37:17 +01:00
akwizgran 033fd2d3b4 Update comments about migration periods, since this isn't merged yet. 2020-06-26 17:37:16 +01:00
akwizgran 011d8e1df7 Remove unused strings. 2020-06-26 17:37:16 +01:00
akwizgran ef5e2dad72 Make inner class just a holder for resources. 2020-06-26 17:37:16 +01:00
akwizgran f35e87c8ad Remove unused code, fix a null safety bug. 2020-06-26 17:37:16 +01:00
akwizgran e4940a046a Move transport toggles back to the settings screen. 2020-06-26 17:37:16 +01:00
akwizgran 0a666df164 Correctly handle connectivity events on IPv6-only networks. 2020-06-26 17:37:16 +01:00
akwizgran 6fb4b95b18 Don't allow list elements to be null. 2020-06-26 17:37:16 +01:00
akwizgran 5567982fb4 Use IPv6 for key agreement if IPv4 isn't available. 2020-06-26 17:37:16 +01:00
akwizgran 25e50ceb10 Try to detect our address on an IPv6-only wifi network. 2020-06-26 17:37:16 +01:00
akwizgran 1495daf977 Remove redundant address checks.
Remote addresses are checked for acceptability before connecting.
2020-06-26 17:37:16 +01:00
akwizgran badc6da649 Accept any link-local IPv6 address.
This allows IPv6 to be used when providing an access point.
2020-06-26 17:37:16 +01:00
akwizgran e065d45d16 Recognise unusual AP addresses. 2020-06-26 17:37:16 +01:00
akwizgran d0c53f1310 Only bind to the SLAAC address of the wifi interface.
This is just a precaution - on all devices I've tested, the wifi
interface is the only one with a SLAAC address.
2020-06-26 17:37:16 +01:00
akwizgran e1084ffadd Support IPv6 SLAAC addresses. 2020-06-26 17:37:15 +01:00
akwizgran 2bd2f67693 Interrupt sync sessions when transport becomes inactive.
This ensures connections are closed when the user disables a transport.
2020-06-26 17:37:12 +01:00
akwizgran c2b0a4b8d1 Remove unnecessary plugin lookup. 2020-06-26 17:34:38 +01:00
akwizgran ee19d2f574 Use tinted icon for plugin settings dialog. 2020-06-26 17:34:38 +01:00
akwizgran e9ec5734e2 Show dialog from controller. 2020-06-26 17:34:38 +01:00
akwizgran 7b1c6f3fdd Add icon, title to Change Settings dialog. 2020-06-26 17:34:38 +01:00
akwizgran d689cf776c Change Tor settings after asking for confirmation. 2020-06-26 17:34:38 +01:00
akwizgran f0fd1844dd Transition from one constraint set to another. 2020-06-26 17:34:38 +01:00
akwizgran d16a301fc4 Make entire collapsed view clickable. 2020-06-26 17:34:38 +01:00
akwizgran 3ab88181eb Put the transport toggles in an expandable view (no animations). 2020-06-26 17:34:37 +01:00
akwizgran 802e599f09 Add STARTING_STOPPING state, use flags for reasons disabled. 2020-06-26 17:34:37 +01:00
akwizgran a6bd59d3c9 Close small gap between setStarted() and setDisabledBySettings(). 2020-06-26 17:34:37 +01:00
akwizgran b04b724028 Don't show Tor in the enabling state if it's disabled by settings. 2020-06-26 17:34:37 +01:00
akwizgran 71b0408fe6 Remove "don't connect" option from Tor network setting.
This has been replaced by the enable/disable setting and no longer
works.
2020-06-26 17:34:37 +01:00
Torsten Grote 2d38bd5734 [android] Scroll down when nav drawer chevron is pressed 2020-06-26 17:34:37 +01:00
Torsten Grote ff5da8404a [android] remove unused strings 2020-06-26 17:34:37 +01:00
Torsten Grote 75615a4e7f [android] make transport plugin toggles functional 2020-06-26 17:34:37 +01:00
Torsten Grote 96e32ad64e [android] Add transport plugin toggles to NavDrawer 2020-06-26 17:34:37 +01:00
Torsten Grote 0fec5d7783 [bramble] Add method for enabling/disabling plugins to PluginManager 2020-06-26 17:34:37 +01:00
akwizgran ee74b3774b Remove another redundant call to pluginStateChanged(). 2020-06-26 17:34:37 +01:00
akwizgran c783a2f352 Enable LAN plugin before showing QR code. 2020-06-26 17:34:37 +01:00
akwizgran 77aa5401f3 Remove redundant call to pluginStateChanged(). 2020-06-26 17:34:37 +01:00
akwizgran 99686f5316 Use XML to specify dependencies between settings. 2020-06-26 17:34:37 +01:00
akwizgran f5b4f6e071 Clean up logic for enabling/disabling settings. 2020-06-26 17:34:36 +01:00
akwizgran a2de841e6a Don't remove old settings yet.
This avoids an unlikely race condition at startup, where the user opens
the settings screen before the Tor plugin has migrated the settings.
2020-06-26 17:34:36 +01:00
akwizgran 1f94c2d4e8 Enable LAN plugin in unit test. 2020-06-26 17:34:36 +01:00
akwizgran 413ce29c0c Enable BT plugin before showing QR code. 2020-06-26 17:34:36 +01:00
akwizgran c67f758c90 Small code cleanups in key agreement UI. 2020-06-26 17:34:36 +01:00
akwizgran 339524500b Make REASON_USER into a generic reason code. 2020-06-26 17:34:36 +01:00
akwizgran 03811f78fa Add toggle setting for LAN plugin. 2020-06-26 17:34:36 +01:00
akwizgran fc86c46456 Update semantics of Bluetooth setting.
The setting now enables/disables the plugin, not just contact
connections. The key agreement UI will need to be updated to change the
setting if the user agrees to use Bluetooth.
2020-06-26 17:34:36 +01:00
akwizgran 7ae86d70af Convert Bluetooth setting to a switch. 2020-06-26 17:34:36 +01:00
akwizgran 63e3c661a3 Add toggle setting for Tor plugin. 2020-06-26 17:34:36 +01:00
akwizgran 4f54bd90fb Remove redundant casts. 2020-06-26 17:34:36 +01:00
akwizgran 706c03aa8b Skip fetching RSS feeds if Tor is not active. 2020-06-26 17:34:36 +01:00
akwizgran c42a987927 Use amber icon when enabling transports. 2020-06-26 17:34:36 +01:00
akwizgran 297dbe0b16 Only update bridge and padding settings if network is enabled. 2020-06-26 17:34:35 +01:00
akwizgran 4130662e1f Notify callback of state changes while holding lock. 2020-06-26 17:34:35 +01:00
akwizgran c08bdf96cd Update javadocs for lock-safe methods. 2020-06-26 17:34:35 +01:00
akwizgran 8bb534564f Remove redundant logging. 2020-06-26 17:34:35 +01:00
akwizgran 5e60a717fc Remove debug logging. 2020-06-26 17:34:35 +01:00
akwizgran dd1509350c Close server socket when BT is disabled. 2020-06-26 17:34:35 +01:00
akwizgran 465ba3d337 Remove unnecessary inner class, state checks. 2020-06-26 17:34:35 +01:00
akwizgran 7561c5039e Reset backoff before notifying of new state.
The new state may cause the poller to poll the
plugin. Let's avoid a race between updating and
querying the polling interval.
2020-06-26 17:34:35 +01:00
akwizgran 242d6f8a0e Move to enabling state earlier in Tor startup. 2020-06-26 17:34:35 +01:00
akwizgran c554847b54 Add TransportStateEvent, rename existing events. 2020-06-26 17:34:35 +01:00
akwizgran d30b250389 Ensure server socket is closed. 2020-06-26 17:34:35 +01:00
akwizgran ecea2c587d Add method for getting reason why plugin is disabled. 2020-06-26 17:34:35 +01:00
akwizgran 43a91e2e57 Fix test expectations. 2020-06-26 17:34:35 +01:00
akwizgran ea288b998b Rename available/unavailable states. 2020-06-26 17:34:34 +01:00
akwizgran 48dc598ca3 Update tests. 2020-06-26 17:34:34 +01:00
akwizgran e2d63ac6a4 If adapter is disabled, forget that we enabled it. 2020-06-26 17:34:34 +01:00
akwizgran afc85cdf52 Check that server sockets are closed as expected. 2020-06-26 17:34:34 +01:00
akwizgran b2a1ea84f8 Provide more information about plugin states. 2020-06-26 17:34:32 +01:00
akwizgran fcc26c093b Avoid NPE if there's no TelephonyManager. 2020-06-26 17:33:54 +01:00
Torsten Grote 5a741bf13b Merge branch '1712-bluetooth-connection-wake-lock' into 'master'
Hold a wake lock while Bluetooth connections are open

See merge request briar/briar!1251
2020-06-26 16:11:36 +00:00
akwizgran 5dc460851b Remove redundant logging. 2020-06-26 15:33:37 +01:00
akwizgran b805514f70 Use renewable wake lock, try to guess a "safe" tag. 2020-06-26 14:58:06 +01:00
akwizgran 69d94c9f29 Hold a wake lock while Bluetooth connections are open. 2020-06-26 14:58:06 +01:00
Torsten Grote 53d4b7a0df Merge branch '1712-simple-connection-limiter' into 'master'
Simple connection limiter that closes connections cleanly

Closes #1712

See merge request briar/briar!1254
2020-06-26 11:36:21 +00:00
akwizgran 648f26542c Simple connection limiter that closes connections cleanly. 2020-06-26 10:57:08 +01:00
akwizgran dcb5f95934 Merge branch '1712-prefer-lan-to-bluetooth' into 'master'
Close redundant connections

See merge request briar/briar!1249
2020-06-26 09:46:24 +00:00
akwizgran 730d553b0a Fix screenshot test (again). 2020-06-26 10:38:04 +01:00
akwizgran 7736a3b6fc Use separate methods for registering incoming and outgoing connections. 2020-06-26 09:59:03 +01:00
akwizgran 95f427863d Remove transport preferences for briar-headless. 2020-06-25 17:46:22 +01:00
Torsten Grote ff8a422638 Merge branch '1712-connection-manager-refactoring' into 'master'
Connection manager refactoring

See merge request briar/briar!1248
2020-06-25 14:21:49 +00:00
akwizgran 78d7fc2106 Fix bug in reporting of connection state, add regression tests. 2020-06-02 12:00:06 +01:00
akwizgran cc943be540 Update javadoc. 2020-06-01 15:30:30 +01:00
akwizgran 6eb77465f6 Don't try to reconnect if the connection was closed cleanly. 2020-06-01 14:49:55 +01:00
akwizgran 35d1b406f7 Refactor transport preferences. 2020-06-01 14:49:55 +01:00
akwizgran 2add63657e Inner class can be static. 2020-06-01 14:49:55 +01:00
akwizgran d3751fbead Don't interrupt connections until priority is set.
This maintains compatibility with older peers that don't know about
priorities or transport preferences and will try to replace any
connections we close.
2020-06-01 14:49:55 +01:00
akwizgran 4aaa8c3b93 Don't poll if already connected via a better transport. 2020-05-25 17:47:33 +01:00
akwizgran 5b04527c54 Fix screenshot test. 2020-05-25 17:47:33 +01:00
akwizgran 7d6b65913a Combine connection chooser with connection registry. 2020-05-25 16:42:01 +01:00
akwizgran 36747acac1 Extract better and worse transports from preferences. 2020-05-25 14:47:34 +01:00
akwizgran e8dbc00712 Refactor connection registry implementation. 2020-05-25 14:33:35 +01:00
akwizgran d3d7212b08 Add registry method for deciding which contacts to poll. 2020-05-13 17:55:05 +01:00
akwizgran 2919657b4a Add unit tests for connection chooser. 2020-05-13 15:56:07 +01:00
akwizgran 0c338b362e Add InterruptibleConnection interface for easier testing. 2020-05-13 15:43:07 +01:00
akwizgran 8dd993dd9d Interrupt connections outside the lock. 2020-05-13 10:24:27 +01:00
akwizgran 1b2b50d91b Exchange priority records and close redundant connections. 2020-05-12 21:36:58 +01:00
akwizgran ee9c771045 Add priority record for choosing between redundant connections. 2020-05-12 21:36:58 +01:00
akwizgran 9e6d67f13d Handle interrupts that occur before the outgoing session starts. 2020-05-12 21:36:57 +01:00
akwizgran 710b6d18ce Fix import in screenshot test. 2020-05-12 21:36:45 +01:00
akwizgran dd4aa67643 Refactor connection creation back into manager. 2020-05-12 21:27:43 +01:00
akwizgran 79482d5e3a Move connection management to its own package. 2020-05-12 17:36:35 +01:00
akwizgran ee0bf7218c Move some duplicated code into utility methods. 2020-05-12 17:25:55 +01:00
akwizgran c1101c7fe1 Factor inner classes out of ConnectionManagerImpl. 2020-05-12 17:08:04 +01:00
Torsten Grote 708452713d Merge branch '1712-detect-dead-bluetooth-connections' into 'master'
Detect and close dead Bluetooth connections

See merge request briar/briar!1246
2020-05-11 15:55:07 +00:00
akwizgran c80d3196af Use milliseconds for timing. 2020-05-11 15:42:23 +01:00
Torsten Grote d1c2eb89a1 Merge branch '1712-fix-double-connection-counting' into 'master'
Don't count Bluetooth connections twice

See merge request briar/briar!1245
2020-05-11 14:06:30 +00:00
akwizgran c4273d22ed Delegate all other methods to wrapped InputStream. 2020-05-08 16:22:46 +01:00
akwizgran 21f3a9f3c7 Add javadoc. 2020-05-08 16:22:46 +01:00
akwizgran 0281eec0da Add unit test for TimeoutInputStream. 2020-05-08 16:22:46 +01:00
akwizgran d3fd309609 Only check timeouts when we have some streams to monitor. 2020-05-08 16:22:46 +01:00
akwizgran f2f278c393 Add timeout monitor for Bluetooth connections. 2020-05-08 16:22:46 +01:00
akwizgran e204d5a996 Don't count connections twice. 2020-05-08 15:17:27 +01:00
akwizgran 876efee1a8 Use keepalives to detect dead connections. 2020-05-08 14:21:41 +01:00
akwizgran 8fd9a40ffb Merge branch 'discover-bt-address-from-incoming-connection' into 'master'
Discover remote Bluetooth address from connection

See merge request briar/briar!1244
2020-04-29 15:31:30 +00:00
akwizgran fb918457d4 Use constants for metadata keys. 2020-04-29 15:37:21 +01:00
akwizgran b5fe55faf3 Validate remote address. 2020-04-29 15:28:27 +01:00
akwizgran 7320099494 Also store properties discovered from outgoing connections.
This is useful when adding a Bluetooth address is discovered while
adding a contact.
2020-04-28 17:56:01 +01:00
akwizgran 346bec94e8 Discover contacts' BT addresses from incoming connections. 2020-04-28 17:45:17 +01:00
akwizgran 856ec61759 Merge branch '1722-lastChatActivity' into 'master'
Include last private chat activity in list of contacts

Closes #1722

See merge request briar/briar!1242
2020-04-27 09:20:08 +00:00
Torsten Grote f61e2b399e [headless] Fix unit tests by passing only timestamp into OutputContact 2020-04-20 09:44:51 -03:00
Nico Alt 6135f9152f Include last private chat activity in list of contacts
Fixes #1722.
2020-04-08 12:00:00 +00:00
Torsten Grote 84584d4d3c Merge branch 'tor-0.3.5.10' into 'master'
Upgrade Tor to version 0.3.5.10

Closes #1714

See merge request briar/briar!1241
2020-03-31 13:14:21 +00:00
akwizgran 17239810c8 Upgrade Tor to version 0.3.5.10. 2020-03-25 17:09:12 +00:00
Torsten Grote 9eee58657e Merge branch '1696-keystore-crash' into 'master'
Show a dialog instead of crashing if a hardware-backed key can't be loaded

Closes #1696

See merge request briar/briar!1233
2020-03-12 12:12:42 +00:00
Torsten Grote 76425455b8 Merge branch 'logging-for-account-bugs' into 'master'
Add logging to track down account bugs

See merge request briar/briar!1239
2020-03-11 14:18:50 +00:00
akwizgran 9ea7140a7f Add logging to track down account bugs. 2020-03-11 14:06:48 +00:00
akwizgran bde9800c89 Add annotation for visibility. 2020-03-11 13:54:01 +00:00
Torsten Grote 4e5b6ed3e0 Merge branch '1367-db-race' into 'master'
Don't infer anything from existence of (possibly empty) DB directory

Closes #1528 and #1367

See merge request briar/briar!1238
2020-03-10 14:59:06 +00:00
akwizgran 77d037f061 Update javadocs. 2020-03-10 11:27:54 +00:00
Torsten Grote 676f5faef4 Merge branch 'fix-wifi-connectivity-misreporting' into 'master'
Fix misreporting of wifi status in LAN plugin

See merge request briar/briar!1237
2020-03-06 16:52:20 +00:00
akwizgran 8e21068465 Fix misreporting of wifi status in LAN plugin. 2020-03-06 13:35:06 +00:00
akwizgran ed50582e27 Show a dialog if the DB key can't be decrypted due to a keystore error. 2020-02-25 15:00:49 +00:00
akwizgran 1546a05568 Catch exception if hardware-backed key can't be loaded. 2020-02-25 12:28:21 +00:00
akwizgran 618ab1f1ec Don't infer anything from existence of (possibly empty) DB directory. 2020-02-24 17:51:59 +00:00
Torsten Grote cf8241e79c Fix IllegalStateException in RecyclerView when backing out very quickly
after adding image attachments for preview before sending
2020-02-13 10:28:00 -03:00
Torsten Grote 61d3fe9055 [android] fix IllegalStateException when creating attachments
Injecting the non-singleton AttachmentCreator keeps an instance around
that gets re-used with a different ViewModel.
When backing out without sending or cancelling the attachments,
we don't reset the state which leads us into an illegal state.
2020-02-13 10:28:00 -03:00
Torsten Grote bded1edb2b [android] Use ordinary HashMap for to be received attachments
Also don't do list stacking from end for now.
2020-02-13 10:26:43 -03:00
akwizgran 4d27828712 Check for concurrent cache updates. 2020-02-13 10:26:43 -03:00
Torsten Grote 0f6f52c37a [android] Listen to AttachmentReceivedEvents when ConversationActivity is stopped
This way Attachments get shown when the activity resumes.
2020-02-13 10:26:42 -03:00
Torsten Grote c1cf6f61b9 [android] fix concurrency issues when attachments are received delayed
Do not observe attachment live data multiple times
and don't miss received attachments in ImageActivity resp. ImageViewModel.
2020-02-13 10:26:42 -03:00
Torsten Grote 7c22016b81 [android] attach some smaller image attachment issues 2020-02-13 10:26:42 -03:00
Torsten Grote 31f42d44af [android] Refactor attachment loading to use LiveData 2020-02-13 10:26:42 -03:00
Torsten Grote a1cf485ecc [android] address first round of code review for attachment placeholders 2020-02-13 10:26:41 -03:00
Torsten Grote b7d3cd7990 [android] support attachments arriving *before* the message containing them 2020-02-13 10:26:41 -03:00
Torsten Grote 4122e0852a Show placeholders for missing attachments in ImageActivity
and display attachments as they arrive while ImageActivity is open.
2020-02-13 10:26:41 -03:00
Torsten Grote 41411b0e2e Refactor attachment loading to support incremental display once loaded 2020-02-13 10:26:40 -03:00
396 changed files with 11960 additions and 6031 deletions
+5 -3
View File
@@ -1,8 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
<option name="IMPORT_LAYOUT_TABLE">
@@ -31,6 +28,11 @@
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value />
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<XML>
+9
View File
@@ -0,0 +1,9 @@
Translations for this project are managed through Transifex:
https://transifex.com/otf/briar
If you'd like to volunteer as a translator, please create a Transifex account and request to be
added to the project's translation team. The Localization Lab has some instructions and advice for
translators here:
https://wiki.localizationlab.org/index.php/Briar
+1
View File
@@ -3,3 +3,4 @@ gen
build
.settings
src/main/res/raw/*.zip
src/main/jniLibs
+40 -9
View File
@@ -5,14 +5,14 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
android {
compileSdkVersion 29
buildToolsVersion '29.0.2'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode 10207
versionName "1.2.7"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -38,7 +38,7 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.9@zip'
tor 'org.briarproject:tor-android:0.3.5.10@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
@@ -53,10 +53,12 @@ dependencies {
}
def torBinariesDir = 'src/main/res/raw'
def torLibsDir = 'src/main/jniLibs'
task cleanTorBinaries {
doLast {
delete fileTree(torBinariesDir) { include '*.zip' }
delete fileTree(torLibsDir) { include '**/*.so' }
}
}
@@ -67,8 +69,36 @@ task unpackTorBinaries {
copy {
from configurations.tor.collect { zipTree(it) }
into torBinariesDir
// TODO: Remove after next Tor upgrade, which won't include non-PIE binaries
include 'geoip.zip', '*_pie.zip'
include 'geoip.zip'
}
configurations.tor.each { outer ->
zipTree(outer).each { inner ->
if (inner.name.endsWith('_arm_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'armeabi-v7a/lib$1.so'
}
} else if (inner.name.endsWith('_arm64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'arm64-v8a/lib$1.so'
}
} else if (inner.name.endsWith('_x86_pie.zip')) {
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
@@ -76,5 +106,6 @@ task unpackTorBinaries {
tasks.withType(MergeResources) {
inputs.dir torBinariesDir
inputs.dir torLibsDir
dependsOn unpackTorBinaries
}
@@ -16,6 +16,8 @@
android:label="@string/app_name"
android:supportsRtl="true">
<receiver android:name=".system.AlarmReceiver" />
</application>
</manifest>
@@ -6,6 +6,8 @@ import org.briarproject.bramble.plugin.tor.CircumventionModule;
import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.system.AndroidSystemModule;
import org.briarproject.bramble.system.AndroidTaskSchedulerModule;
import org.briarproject.bramble.system.AndroidWakefulIoExecutorModule;
import dagger.Module;
@@ -13,6 +15,8 @@ import dagger.Module;
AndroidBatteryModule.class,
AndroidNetworkModule.class,
AndroidSystemModule.class,
AndroidTaskSchedulerModule.class,
AndroidWakefulIoExecutorModule.class,
CircumventionModule.class,
ReportingModule.class,
SocksModule.class
@@ -0,0 +1,8 @@
package org.briarproject.bramble;
import org.briarproject.bramble.api.system.AlarmListener;
public interface BrambleAppComponent {
AlarmListener alarmListener();
}
@@ -0,0 +1,6 @@
package org.briarproject.bramble;
public interface BrambleApplication {
BrambleAppComponent getBrambleAppComponent();
}
@@ -0,0 +1,11 @@
package org.briarproject.bramble.api.system;
import android.content.Intent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface AlarmListener {
void onAlarm(Intent intent);
}
@@ -0,0 +1,19 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.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();
}
@@ -0,0 +1,38 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.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);
}
@@ -9,16 +9,17 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -50,20 +51,22 @@ class AndroidNetworkManager implements NetworkManager, Service {
private static final String WIFI_AP_STATE_CHANGED_ACTION =
"android.net.wifi.WIFI_AP_STATE_CHANGED";
private final ScheduledExecutorService scheduler;
private final TaskScheduler scheduler;
private final EventBus eventBus;
private final Executor eventExecutor;
private final Context appContext;
private final AtomicReference<Future<?>> connectivityCheck =
private final AtomicReference<Cancellable> connectivityCheck =
new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile BroadcastReceiver networkStateReceiver = null;
@Inject
AndroidNetworkManager(@Scheduler ScheduledExecutorService scheduler,
EventBus eventBus, Application app) {
AndroidNetworkManager(TaskScheduler scheduler, EventBus eventBus,
@EventExecutor Executor eventExecutor, Application app) {
this.scheduler = scheduler;
this.eventBus = eventBus;
this.eventExecutor = eventExecutor;
this.appContext = app.getApplicationContext();
}
@@ -104,11 +107,12 @@ class AndroidNetworkManager implements NetworkManager, Service {
}
private void scheduleConnectionStatusUpdate(int delay, TimeUnit unit) {
Future<?> newConnectivityCheck =
scheduler.schedule(this::updateConnectionStatus, delay, unit);
Future<?> oldConnectivityCheck =
Cancellable newConnectivityCheck =
scheduler.schedule(this::updateConnectionStatus, eventExecutor,
delay, unit);
Cancellable oldConnectivityCheck =
connectivityCheck.getAndSet(newConnectivityCheck);
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel();
}
private class NetworkStateReceiver extends BroadcastReceiver {
@@ -0,0 +1,36 @@
package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import java.io.IOException;
@NotNullByDefault
class AndroidBluetoothConnectionFactory
implements BluetoothConnectionFactory<BluetoothSocket> {
private final BluetoothConnectionLimiter connectionLimiter;
private final AndroidWakeLockManager wakeLockManager;
private final TimeoutMonitor timeoutMonitor;
AndroidBluetoothConnectionFactory(
BluetoothConnectionLimiter connectionLimiter,
AndroidWakeLockManager wakeLockManager,
TimeoutMonitor timeoutMonitor) {
this.connectionLimiter = connectionLimiter;
this.wakeLockManager = wakeLockManager;
this.timeoutMonitor = timeoutMonitor;
}
@Override
public DuplexTransportConnection wrapSocket(DuplexPlugin plugin,
BluetoothSocket s) throws IOException {
return new AndroidBluetoothTransportConnection(plugin,
connectionLimiter, wakeLockManager, timeoutMonitor, s);
}
}
@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.bluetooth;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
@@ -8,34 +9,23 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.os.Parcelable;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.IoUtils;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
@@ -57,55 +47,52 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.ACTION_UUID;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.bluetooth.BluetoothDevice.EXTRA_UUID;
import static android.os.Build.VERSION.SDK_INT;
import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.shuffle;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
class AndroidBluetoothPlugin
extends BluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
private static final Logger LOG =
getLogger(AndroidBluetoothPlugin.class.getName());
private static final int MIN_DEVICE_DISCOVERY_MS = 2_000;
private static final int MAX_DEVICE_DISCOVERY_MS = 30_000;
private static final int MAX_SERVICE_DISCOVERY_MS = 15_000;
private static final int MAX_DISCOVERY_MS = 10_000;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final Application app;
private final Clock clock;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
// Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Clock clock,
Backoff backoff, PluginCallback callback, int maxLatency) {
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
maxLatency);
BluetoothConnectionFactory<BluetoothSocket> connectionFactory,
Executor ioExecutor,
Executor wakefulIoExecutor,
SecureRandom secureRandom,
AndroidExecutor androidExecutor,
Application app,
Clock clock,
Backoff backoff,
PluginCallback callback,
int maxLatency,
int maxIdleTime) {
super(connectionLimiter, connectionFactory, ioExecutor,
wakefulIoExecutor, secureRandom, backoff, callback,
maxLatency, maxIdleTime);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.app = app;
this.clock = clock;
}
@@ -117,13 +104,13 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
receiver = new BluetoothStateReceiver();
appContext.registerReceiver(receiver, filter);
app.registerReceiver(receiver, filter);
}
@Override
public void stop() {
super.stop();
if (receiver != null) appContext.unregisterReceiver(receiver);
if (receiver != null) app.unregisterReceiver(receiver);
}
@Override
@@ -145,61 +132,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return adapter != null && adapter.isEnabled();
}
@Override
void enableAdapter() {
if (adapter != null && !adapter.isEnabled()) {
if (adapter.enable()) {
LOG.info("Enabling Bluetooth");
wasEnabledByUs = true;
} else {
LOG.info("Could not enable Bluetooth");
}
}
}
@Override
void disableAdapterIfEnabledByUs() {
if (isAdapterEnabled() && wasEnabledByUs) {
cancelDiscoverability();
if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth");
wasEnabledByUs = false;
}
}
private void cancelDiscoverability() {
if (adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
try {
Method setDiscoverableTimeout = BluetoothAdapter.class
.getDeclaredMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, 1);
LOG.info("Cancelled discoverability");
} catch (NoSuchMethodException e) {
logException(LOG, WARNING, e);
} catch (IllegalAccessException e) {
logException(LOG, WARNING, e);
} catch (InvocationTargetException e) {
logException(LOG, WARNING, e);
}
}
}
@Override
void setEnabledByUs() {
wasEnabledByUs = true;
}
@Override
void onAdapterDisabled() {
super.onAdapterDisabled();
wasEnabledByUs = false;
}
@Override
@Nullable
String getBluetoothAddress() {
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
if (adapter == null) return null;
String address = AndroidUtils.getBluetoothAddress(app, adapter);
return address.isEmpty() ? null : address;
}
@@ -217,12 +154,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
throws IOException {
return wrapSocket(ss.accept());
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this,
connectionLimiter, s);
return connectionFactory.wrapSocket(this, ss.accept());
}
@Override
@@ -239,7 +171,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
try {
s = d.createInsecureRfcommSocketToServiceRecord(u);
s.connect();
return wrapSocket(s);
return connectionFactory.wrapSocket(this, s);
} catch (IOException e) {
IoUtils.tryToClose(s, LOG, WARNING);
throw e;
@@ -250,8 +182,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Nullable
DuplexTransportConnection discoverAndConnect(String uuid) {
if (adapter == null) return null;
for (BluetoothDevice d : discoverDevices()) {
String address = d.getAddress();
for (String address : discoverDevices()) {
try {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
@@ -267,194 +198,19 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return null;
}
@Override
public boolean supportsDiscovery() {
return true;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
// Discover all nearby devices
List<BluetoothDevice> devices = discoverDevices();
if (devices.isEmpty()) {
LOG.info("No devices discovered");
return;
}
List<Pair<TransportProperties, DiscoveryHandler>> discovered =
new ArrayList<>();
Map<String, Pair<TransportProperties, DiscoveryHandler>> byUuid =
new HashMap<>();
Map<String, Pair<TransportProperties, DiscoveryHandler>> byAddress =
new HashMap<>();
for (Pair<TransportProperties, DiscoveryHandler> pair : properties) {
TransportProperties p = pair.getFirst();
String uuid = p.get(PROP_UUID);
if (!isNullOrEmpty(uuid)) {
byUuid.put(uuid, pair);
String address = p.get(PROP_ADDRESS);
if (!isNullOrEmpty(address)) byAddress.put(address, pair);
}
}
List<BluetoothDevice> unknown = new ArrayList<>(devices);
for (BluetoothDevice d : devices) {
Pair<TransportProperties, DiscoveryHandler> pair =
byAddress.remove(d.getAddress());
if (pair == null) {
// Try cached UUIDs
for (String uuid : getUuids(d)) {
pair = byUuid.remove(uuid);
if (pair != null) {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched "
+ scrubMacAddress(d.getAddress())
+ " by cached UUID");
}
TransportProperties p =
new TransportProperties(pair.getFirst());
p.put(PROP_ADDRESS, d.getAddress());
discovered.add(new Pair<>(p, pair.getSecond()));
unknown.remove(d);
break;
}
}
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched " + scrubMacAddress(d.getAddress())
+ " by address");
}
discovered.add(pair);
unknown.remove(d);
}
}
if (unknown.isEmpty()) {
LOG.info("All discovered devices are known, not fetching UUIDs");
return;
}
// Fetch up-to-date UUIDs
if (LOG.isLoggable(INFO))
LOG.info("Fetching UUIDs for " + unknown.size() + " devices");
private Collection<String> discoverDevices() {
List<String> addresses = new ArrayList<>();
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
QueueingReceiver receiver = new QueueingReceiver(intents);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_UUID);
appContext.registerReceiver(receiver, filter);
try {
List<BluetoothDevice> pending = new ArrayList<>();
for (BluetoothDevice d : unknown) {
if (d.fetchUuidsWithSdp()) {
if (LOG.isLoggable(INFO)) {
LOG.info("Fetching UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
pending.add(d);
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Failed to fetch UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
}
}
long now = clock.currentTimeMillis();
long end = now + MAX_SERVICE_DISCOVERY_MS;
while (now < end && !pending.isEmpty()) {
Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break;
BluetoothDevice d = requireNonNull(
i.getParcelableExtra(EXTRA_DEVICE));
if (LOG.isLoggable(INFO)) {
LOG.info("Fetched UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
Set<String> uuids = getUuids(d);
Parcelable[] extra = i.getParcelableArrayExtra(EXTRA_UUID);
if (extra != null) {
for (Parcelable p : extra) {
uuids.addAll(getUuidStrings((ParcelUuid) p));
}
}
for (String uuid : uuids) {
Pair<TransportProperties, DiscoveryHandler> pair =
byUuid.remove(uuid);
if (pair != null) {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched "
+ scrubMacAddress(d.getAddress())
+ " by fetched UUID");
}
TransportProperties p =
new TransportProperties(pair.getFirst());
p.put(PROP_ADDRESS, d.getAddress());
discovered.add(new Pair<>(p, pair.getSecond()));
break;
}
}
pending.remove(d);
now = clock.currentTimeMillis();
}
if (LOG.isLoggable(INFO)) {
if (pending.isEmpty()) {
LOG.info("Finished fetching UUIDs");
} else {
LOG.info("Failed to fetch UUIDs for " + pending.size()
+ " devices");
}
}
} catch (InterruptedException e) {
LOG.info("Interrupted while fetching UUIDs");
Thread.currentThread().interrupt();
} finally {
appContext.unregisterReceiver(receiver);
}
if (LOG.isLoggable(INFO)) {
LOG.info("Discovered " + discovered.size() + " contacts");
}
for (Pair<TransportProperties, DiscoveryHandler> pair : discovered) {
pair.getSecond().handleDevice(pair.getFirst());
}
}
private Set<String> getUuids(BluetoothDevice d) {
Set<String> strings = new TreeSet<>();
ParcelUuid[] uuids = d.getUuids();
if (uuids == null) return strings;
for (ParcelUuid u : uuids) strings.addAll(getUuidStrings(u));
return strings;
}
// Workaround for https://code.google.com/p/android/issues/detail?id=197341
private List<String> getUuidStrings(ParcelUuid u) {
UUID forwards = u.getUuid();
ByteBuffer buf = ByteBuffer.allocate(16);
buf.putLong(forwards.getLeastSignificantBits());
buf.putLong(forwards.getMostSignificantBits());
buf.rewind();
buf.order(LITTLE_ENDIAN);
UUID backwards = new UUID(buf.getLong(), buf.getLong());
return asList(forwards.toString(), backwards.toString());
}
private List<BluetoothDevice> discoverDevices() {
if (adapter.isDiscovering()) {
LOG.info("Already discovering");
return emptyList();
}
List<BluetoothDevice> devices = new ArrayList<>();
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
QueueingReceiver receiver = new QueueingReceiver(intents);
DiscoveryReceiver receiver = new DiscoveryReceiver(intents);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCOVERY_STARTED);
filter.addAction(ACTION_DISCOVERY_FINISHED);
filter.addAction(ACTION_FOUND);
appContext.registerReceiver(receiver, filter);
app.registerReceiver(receiver, filter);
try {
if (adapter.startDiscovery()) {
long start = clock.currentTimeMillis();
long end = start + MAX_DEVICE_DISCOVERY_MS;
long now = start;
long now = clock.currentTimeMillis();
long end = now + MAX_DISCOVERY_MS;
while (now < end) {
Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break;
@@ -463,26 +219,17 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
LOG.info("Discovery started");
} else if (ACTION_DISCOVERY_FINISHED.equals(action)) {
LOG.info("Discovery finished");
now = clock.currentTimeMillis();
if (now - start < MIN_DEVICE_DISCOVERY_MS) {
LOG.info("Discovery finished quickly, retrying");
if (!adapter.startDiscovery()) {
LOG.info("Could not restart discovery");
break;
}
} else {
break;
}
} else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = requireNonNull(
i.getParcelableExtra(EXTRA_DEVICE));
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
// Ignore Bluetooth LE devices
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) {
if (LOG.isLoggable(INFO)) {
LOG.info("Discovered "
+ scrubMacAddress(d.getAddress()));
}
if (!devices.contains(d)) devices.add(d);
String address = d.getAddress();
if (LOG.isLoggable(INFO))
LOG.info("Discovered " +
scrubMacAddress(address));
if (!addresses.contains(address))
addresses.add(address);
}
}
now = clock.currentTimeMillis();
@@ -496,11 +243,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
} finally {
LOG.info("Cancelling discovery");
adapter.cancelDiscovery();
appContext.unregisterReceiver(receiver);
app.unregisterReceiver(receiver);
}
// Shuffle the devices so we don't always try the same one first
shuffle(devices);
return devices;
// Shuffle the addresses so we don't always try the same one first
shuffle(addresses);
return addresses;
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@@ -521,11 +268,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
}
}
private static class QueueingReceiver extends BroadcastReceiver {
private static class DiscoveryReceiver extends BroadcastReceiver {
private final BlockingQueue<Intent> intents;
private QueueingReceiver(BlockingQueue<Intent> intents) {
private DiscoveryReceiver(BlockingQueue<Intent> intents) {
this.intents = intents;
}
@@ -1,8 +1,11 @@
package org.briarproject.bramble.plugin.bluetooth;
import android.content.Context;
import android.app.Application;
import android.bluetooth.BluetoothSocket;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -11,12 +14,15 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
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.WakefulIoExecutor;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@@ -25,28 +31,41 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
private final Executor ioExecutor, wakefulIoExecutor;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final AndroidWakeLockManager wakeLockManager;
private final Application app;
private final SecureRandom secureRandom;
private final EventBus eventBus;
private final Clock clock;
private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, Clock clock,
@Inject
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
AndroidExecutor androidExecutor,
AndroidWakeLockManager wakeLockManager,
Application app,
SecureRandom secureRandom,
EventBus eventBus,
Clock clock,
TimeoutMonitor timeoutMonitor,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.wakeLockManager = wakeLockManager;
this.app = app;
this.secureRandom = secureRandom;
this.eventBus = eventBus;
this.clock = clock;
this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory;
}
@@ -63,12 +82,16 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl();
new BluetoothConnectionLimiterImpl(eventBus);
BluetoothConnectionFactory<BluetoothSocket> connectionFactory =
new AndroidBluetoothConnectionFactory(connectionLimiter,
wakeLockManager, timeoutMonitor);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, ioExecutor, androidExecutor, appContext,
secureRandom, clock, backoff, callback, MAX_LATENCY);
connectionLimiter, connectionFactory, ioExecutor,
wakefulIoExecutor, secureRandom, androidExecutor, app,
clock, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
}
@@ -2,32 +2,48 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress;
@NotNullByDefault
class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager;
private final BluetoothConnectionLimiter connectionLimiter;
private final BluetoothSocket socket;
private final InputStream in;
private final AndroidWakeLock wakeLock;
AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager,
BluetoothSocket socket) {
BluetoothConnectionLimiter connectionLimiter,
AndroidWakeLockManager wakeLockManager,
TimeoutMonitor timeoutMonitor,
BluetoothSocket socket) throws IOException {
super(plugin);
this.connectionManager = connectionManager;
this.connectionLimiter = connectionLimiter;
this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2);
wakeLock = wakeLockManager.createWakeLock("BluetoothConnection");
wakeLock.acquire();
String address = socket.getRemoteDevice().getAddress();
if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address);
}
@Override
protected InputStream getInputStream() throws IOException {
return socket.getInputStream();
protected InputStream getInputStream() {
return in;
}
@Override
@@ -39,8 +55,10 @@ class AndroidBluetoothTransportConnection
protected void closeConnection(boolean exception) throws IOException {
try {
socket.close();
in.close();
} finally {
connectionManager.connectionClosed(this);
wakeLock.release();
connectionLimiter.connectionClosed(this);
}
}
}
@@ -1,13 +1,17 @@
package org.briarproject.bramble.plugin.tcp;
import android.content.Context;
import android.annotation.TargetApi;
import android.app.Application;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -17,7 +21,10 @@ import org.briarproject.bramble.api.settings.Settings;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.List;
import java.util.concurrent.Executor;
@@ -28,15 +35,19 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.net.ConnectivityManager.TYPE_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.list;
import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin {
@@ -51,20 +62,22 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
private volatile SocketFactory socketFactory;
AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
Backoff backoff, PluginCallback callback, int maxLatency,
int maxIdleTime, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
AndroidLanTcpPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
Application app,
Backoff backoff,
PluginCallback callback,
int maxLatency,
int maxIdleTime,
int connectionTimeout) {
super(ioExecutor, wakefulIoExecutor, backoff, callback, maxLatency,
maxIdleTime, connectionTimeout);
// Don't execute more than one connection status check at a time
connectionStatusExecutor =
new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1);
ConnectivityManager connectivityManager = (ConnectivityManager)
appContext.getSystemService(CONNECTIVITY_SERVICE);
if (connectivityManager == null) throw new AssertionError();
this.connectivityManager = connectivityManager;
wifiManager = (WifiManager) appContext.getApplicationContext()
.getSystemService(WIFI_SERVICE);
connectivityManager = (ConnectivityManager)
requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE));
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
socketFactory = SocketFactory.getDefault();
}
@@ -73,7 +86,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty();
Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE,
DEFAULT_PREF_PLUGIN_ENABLE));
updateConnectionStatus();
}
@@ -83,21 +97,125 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
}
@Override
protected List<InetAddress> getUsableLocalInetAddresses() {
// If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList();
protected List<InetAddress> getUsableLocalInetAddresses(boolean ipv4) {
InetAddress addr = getWifiAddress(ipv4);
return addr == null ? emptyList() : singletonList(addr);
}
@Nullable
private InetAddress getWifiAddress(boolean ipv4) {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
if (ipv4) return wifi == null ? null : wifi.getFirst();
// 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+
if (wifi == null) {
return SDK_INT >= 21 ? getWifiClientIpv6Address() : null;
}
// Use the wifi IPv4 address to determine which interface's IPv6
// address we should return (if the interface has a suitable address)
return getIpv6AddressForInterface(wifi.getFirst());
}
/**
* Returns a {@link Pair} where the first element is the IPv4 address of
* the wifi interface and the second element is true if this device is
* providing an access point, or false if this device is a client. Returns
* null if this device isn't connected to wifi as an access point or client.
*/
@Nullable
private Pair<InetAddress, Boolean> getWifiIpv4Address() {
if (wifiManager == null) return null;
// If we're connected to a wifi network, return its address
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0) {
return singletonList(intToInetAddress(info.getIpAddress()));
return new Pair<>(intToInetAddress(info.getIpAddress()), false);
}
// If we're running an access point, return its address
for (InetAddress addr : getLocalInetAddresses()) {
if (addr.equals(WIFI_AP_ADDRESS)) return singletonList(addr);
if (addr.equals(WIFI_DIRECT_AP_ADDRESS)) return singletonList(addr);
List<InterfaceAddress> ifAddrs = getLocalInterfaceAddresses();
// If we're providing a normal access point, return its address
for (InterfaceAddress ifAddr : ifAddrs) {
if (isAndroidWifiApAddress(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true);
}
}
// If we're providing a wifi direct access point, return its address
for (InterfaceAddress ifAddr : ifAddrs) {
if (isAndroidWifiDirectApAddress(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true);
}
}
// Not connected to wifi
return null;
}
/**
* Returns true if the given address belongs to a network provided by an
* Android access point (including the access point's own address).
* <p>
* The access point's address is usually 192.168.43.1, but at least one
* device (Honor 8A) may use other addresses in the range 192.168.43.0/24.
*/
private boolean isAndroidWifiApAddress(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4
&& ip[0] == (byte) 192
&& ip[1] == (byte) 168
&& ip[2] == (byte) 43;
}
/**
* Returns true if the given address belongs to a network provided by an
* Android wifi direct legacy mode access point (including the access
* point's own address).
*/
private boolean isAndroidWifiDirectApAddress(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4
&& ip[0] == (byte) 192
&& ip[1] == (byte) 168
&& ip[2] == (byte) 49;
}
/**
* 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.
*/
@TargetApi(21)
@Nullable
private InetAddress getWifiClientIpv6Address() {
for (Network net : connectivityManager.getAllNetworks()) {
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) continue;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (isIpv6LinkLocalAddress(addr)) return addr;
}
}
return null;
}
/**
* Returns a link-local IPv6 address for the interface with the given IPv4
* address, or null if the interface doesn't have a suitable address.
*/
@Nullable
private InetAddress getIpv6AddressForInterface(InetAddress ipv4) {
try {
NetworkInterface iface = NetworkInterface.getByInetAddress(ipv4);
if (iface == null) return null;
for (InetAddress addr : list(iface.getInetAddresses())) {
if (isIpv6LinkLocalAddress(addr)) return addr;
}
// No suitable address
return null;
} catch (SocketException e) {
logException(LOG, WARNING, e);
return null;
}
// No suitable addresses
return emptyList();
}
private InetAddress intToInetAddress(int ip) {
@@ -119,10 +237,12 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault();
for (Network net : connectivityManager.getAllNetworks()) {
NetworkInfo info = connectivityManager.getNetworkInfo(net);
if (info != null && info.getType() == TYPE_WIFI)
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
return net.getSocketFactory();
}
}
LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault();
}
@@ -137,31 +257,51 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
connectionStatusExecutor.execute(() -> {
State s = getState();
if (s != ACTIVE && s != INACTIVE) return;
List<InetAddress> addrs = getLocalInetAddresses();
if (addrs.contains(WIFI_AP_ADDRESS)
|| addrs.contains(WIFI_DIRECT_AP_ADDRESS)) {
Pair<InetAddress, Boolean> wifi = getPreferredWifiAddress();
if (wifi == null) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
// Server sockets may not have been closed automatically when
// interface was taken down. If any sockets are open, closing
// them here will cause the sockets to be cleared and the state
// to be updated in acceptContactConnections()
if (s == ACTIVE) {
LOG.info("Closing server sockets");
tryToClose(state.getServerSocket(true), LOG, WARNING);
tryToClose(state.getServerSocket(false), LOG, WARNING);
}
} else if (wifi.getSecond()) {
LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to
// make outgoing connections on API 21+ if another network
// has internet access
socketFactory = SocketFactory.getDefault();
if (s == INACTIVE) bind();
} else if (addrs.isEmpty()) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
// Server socket may not have been closed automatically when
// interface was taken down. Socket will be cleared and state
// updated in acceptContactConnections()
if (s == ACTIVE) {
LOG.info("Closing server socket");
tryToClose(state.getServerSocket(), LOG, WARNING);
}
bind();
} else {
LOG.info("Connected to wifi");
socketFactory = getSocketFactory();
if (s == INACTIVE) bind();
bind();
}
});
}
/**
* Returns a {@link Pair} where the first element is an IP address (IPv4 if
* available, otherwise IPv6) of the wifi interface and the second element
* is true if this device is providing an access point, or false if this
* device is a client. Returns null if this device isn't connected to wifi
* as an access point or client.
*/
@Nullable
private Pair<InetAddress, Boolean> getPreferredWifiAddress() {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
// 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+
if (wifi == null && SDK_INT >= 21) {
InetAddress ipv6 = getWifiClientIpv6Address();
if (ipv6 != null) return new Pair<>(ipv6, false);
}
return wifi;
}
}
@@ -1,8 +1,9 @@
package org.briarproject.bramble.plugin.tcp;
import android.content.Context;
import android.app.Application;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -10,10 +11,12 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@@ -28,17 +31,22 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
private final Executor ioExecutor, wakefulIoExecutor;
private final EventBus eventBus;
private final BackoffFactory backoffFactory;
private final Context appContext;
private final Application app;
public AndroidLanTcpPluginFactory(Executor ioExecutor, EventBus eventBus,
BackoffFactory backoffFactory, Context appContext) {
@Inject
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
EventBus eventBus,
BackoffFactory backoffFactory,
Application app) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.eventBus = eventBus;
this.backoffFactory = backoffFactory;
this.appContext = appContext;
this.app = app;
}
@Override
@@ -56,8 +64,8 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME,
CONNECTION_TIMEOUT);
wakefulIoExecutor, app, backoff, callback,
MAX_LATENCY, MAX_IDLE_TIME, CONNECTION_TIMEOUT);
eventBus.addListener(plugin);
return plugin;
}
@@ -1,10 +1,9 @@
package org.briarproject.bramble.plugin.tor;
import android.content.Context;
import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.PowerManager;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
@@ -12,48 +11,76 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
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.RenewableWakeLock;
import org.briarproject.bramble.util.AndroidUtils;
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.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.net.SocketFactory;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MINUTES;
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 final Context appContext;
private final RenewableWakeLock wakeLock;
private static final List<String> LIBRARY_ARCHITECTURES =
asList("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
AndroidTorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
Context appContext, NetworkManager networkManager,
LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider,
private static final String TOR_LIB_NAME = "libtor.so";
private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
private static final Logger LOG =
getLogger(AndroidTorPlugin.class.getName());
private final Application app;
private final AndroidWakeLock wakeLock;
private final File torLib, obfs4Lib;
AndroidTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
Application app,
NetworkManager networkManager,
LocationUtils locationUtils,
SocketFactory torSocketFactory,
Clock clock,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Backoff backoff,
BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, String architecture, int maxLatency,
int maxIdleTime) {
super(ioExecutor, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture, maxLatency, maxIdleTime,
appContext.getDir("tor", MODE_PRIVATE));
this.appContext = appContext;
PowerManager pm = (PowerManager)
appContext.getSystemService(POWER_SERVICE);
if (pm == null) throw new AssertionError();
wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK,
getWakeLockTag(), 1, MINUTES);
PluginCallback callback,
String architecture,
int maxLatency,
int maxIdleTime,
File torDirectory) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture, maxLatency,
maxIdleTime, torDirectory);
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);
}
@Override
@@ -64,8 +91,8 @@ class AndroidTorPlugin extends TorPlugin {
@Override
protected long getLastUpdateTime() {
try {
PackageManager pm = appContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(appContext.getPackageName(), 0);
PackageManager pm = app.getPackageManager();
PackageInfo pi = pm.getPackageInfo(app.getPackageName(), 0);
return pi.lastUpdateTime;
} catch (NameNotFoundException e) {
throw new AssertionError(e);
@@ -85,16 +112,111 @@ class AndroidTorPlugin extends TorPlugin {
wakeLock.release();
}
private String getWakeLockTag() {
PackageManager pm = appContext.getPackageManager();
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
@Override
protected File getTorExecutableFile() {
return torLib.exists() ? torLib : super.getTorExecutableFile();
}
@Override
protected File getObfs4ExecutableFile() {
return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
}
@Override
protected void installTorExecutable() throws IOException {
File extracted = super.getTorExecutableFile();
if (torLib.exists()) {
// If an older version left behind a Tor binary, delete it
if (extracted.exists()) {
if (extracted.delete()) LOG.info("Deleted Tor binary");
else LOG.info("Failed to delete Tor binary");
}
} else if (SDK_INT < 29) {
// The binary wasn't extracted at install time. Try to extract it
extractLibraryFromApk(TOR_LIB_NAME, extracted);
} else {
// No point extracting the binary, we won't be allowed to execute it
throw new FileNotFoundException(torLib.getAbsolutePath());
}
}
return getClass().getSimpleName();
@Override
protected void installObfs4Executable() throws IOException {
File extracted = super.getObfs4ExecutableFile();
if (obfs4Lib.exists()) {
// If an older version left behind an obfs4 binary, delete it
if (extracted.exists()) {
if (extracted.delete()) LOG.info("Deleted obfs4 binary");
else LOG.info("Failed to delete obfs4 binary");
}
} else if (SDK_INT < 29) {
// The binary wasn't extracted at install time. Try to extract it
extractLibraryFromApk(OBFS4_LIB_NAME, extracted);
} else {
// No point extracting the binary, we won't be allowed to execute it
throw new FileNotFoundException(obfs4Lib.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;
}
}
@@ -1,28 +1,33 @@
package org.briarproject.bramble.plugin.tor;
import android.content.Context;
import android.app.Application;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
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.api.system.WakefulIoExecutor;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
@Immutable
@@ -38,9 +43,8 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext;
private final Executor ioExecutor, wakefulIoExecutor;
private final Application app;
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final EventBus eventBus;
@@ -49,18 +53,28 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
private final ResourceProvider resourceProvider;
private final CircumventionProvider circumventionProvider;
private final BatteryManager batteryManager;
private final AndroidWakeLockManager wakeLockManager;
private final Clock clock;
private final File torDirectory;
public AndroidTorPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler, Context appContext,
NetworkManager networkManager, LocationUtils locationUtils,
EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, ResourceProvider resourceProvider,
@Inject
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
Application app,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Clock clock) {
BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager,
Clock clock,
@TorDirectory File torDirectory) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext;
this.wakefulIoExecutor = wakefulIoExecutor;
this.app = app;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
@@ -69,7 +83,9 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.wakeLockManager = wakeLockManager;
this.clock = clock;
this.torDirectory = torDirectory;
}
@Override
@@ -112,11 +128,12 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor, scheduler,
appContext, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, batteryManager,
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
wakefulIoExecutor, app, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, wakeLockManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME);
MAX_LATENCY, MAX_IDLE_TIME, torDirectory);
eventBus.addListener(plugin);
return plugin;
}
@@ -0,0 +1,18 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface AlarmConstants {
/**
* Request code for the broadcast intent attached to the periodic alarm.
*/
int REQUEST_ALARM = 1;
/**
* Key for storing the process ID in the extras of the periodic alarm's
* intent. This allows us to ignore alarms scheduled by dead processes.
*/
String EXTRA_PID = "org.briarproject.bramble.EXTRA_PID";
}
@@ -0,0 +1,17 @@
package org.briarproject.bramble.system;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.briarproject.bramble.BrambleApplication;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
BrambleApplication app =
(BrambleApplication) ctx.getApplicationContext();
app.getBrambleAppComponent().alarmListener().onAlarm(intent);
}
}
@@ -1,12 +1,17 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
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.SecureRandomProvider;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import javax.inject.Singleton;
@@ -16,6 +21,23 @@ import dagger.Provides;
@Module
public class AndroidSystemModule {
private final ScheduledExecutorService scheduledExecutorService;
public AndroidSystemModule() {
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
scheduledExecutorService = new ScheduledThreadPoolExecutor(1, policy);
}
@Provides
@Singleton
ScheduledExecutorService provideScheduledExecutorService(
LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(scheduledExecutorService);
return scheduledExecutorService;
}
@Provides
@Singleton
SecureRandomProvider provideSecureRandomProvider(
@@ -47,4 +69,11 @@ public class AndroidSystemModule {
ResourceProvider provideResourceProvider(AndroidResourceProvider provider) {
return provider;
}
@Provides
@Singleton
AndroidWakeLockManager provideWakeLockManager(
AndroidWakeLockManagerImpl wakeLockManager) {
return wakeLockManager;
}
}
@@ -0,0 +1,242 @@
package org.briarproject.bramble.system;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Process;
import android.os.SystemClock;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.Wakeful;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.INTERVAL_FIFTEEN_MINUTES;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.content.Context.ALARM_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.system.AlarmConstants.EXTRA_PID;
import static org.briarproject.bramble.system.AlarmConstants.REQUEST_ALARM;
@ThreadSafe
@NotNullByDefault
class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
private static final Logger LOG =
getLogger(AndroidTaskScheduler.class.getName());
private static final long ALARM_MS = INTERVAL_FIFTEEN_MINUTES;
private final Application app;
private final AndroidWakeLockManager wakeLockManager;
private final ScheduledExecutorService scheduledExecutorService;
private final AlarmManager alarmManager;
private final Object lock = new Object();
@GuardedBy("lock")
private final Queue<ScheduledTask> tasks = new PriorityQueue<>();
AndroidTaskScheduler(Application app,
AndroidWakeLockManager wakeLockManager,
ScheduledExecutorService scheduledExecutorService) {
this.app = app;
this.wakeLockManager = wakeLockManager;
this.scheduledExecutorService = scheduledExecutorService;
alarmManager = (AlarmManager)
requireNonNull(app.getSystemService(ALARM_SERVICE));
}
@Override
public void startService() {
scheduleAlarm();
}
@Override
public void stopService() {
cancelAlarm();
}
@Override
public Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit) {
AtomicBoolean cancelled = new AtomicBoolean(false);
return schedule(task, executor, delay, unit, cancelled);
}
@Override
public Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit) {
AtomicBoolean cancelled = new AtomicBoolean(false);
return scheduleWithFixedDelay(task, executor, delay, interval, unit,
cancelled);
}
@Override
public void onAlarm(Intent intent) {
wakeLockManager.runWakefully(() -> {
int extraPid = intent.getIntExtra(EXTRA_PID, -1);
int currentPid = Process.myPid();
if (extraPid == currentPid) {
LOG.info("Alarm");
rescheduleAlarm();
runDueTasks();
} else if (LOG.isLoggable(INFO)) {
LOG.info("Ignoring alarm with PID " + extraPid
+ ", current PID is " + currentPid);
}
}, "TaskAlarm");
}
private Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit, AtomicBoolean cancelled) {
long now = SystemClock.elapsedRealtime();
long dueMillis = now + MILLISECONDS.convert(delay, unit);
Runnable wakeful = () ->
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
Future<?> check = scheduleCheckForDueTasks(delay, unit);
ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
cancelled);
synchronized (lock) {
tasks.add(s);
}
return s;
}
private Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit, AtomicBoolean cancelled) {
// All executions of this periodic task share a cancelled flag
Runnable wrapped = () -> {
task.run();
scheduleWithFixedDelay(task, executor, interval, interval, unit,
cancelled);
};
return schedule(wrapped, executor, delay, unit, cancelled);
}
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
Runnable wakeful = () -> wakeLockManager.runWakefully(
this::runDueTasks, "TaskScheduler");
return scheduledExecutorService.schedule(wakeful, delay, unit);
}
@Wakeful
private void runDueTasks() {
long now = SystemClock.elapsedRealtime();
List<ScheduledTask> due = new ArrayList<>();
synchronized (lock) {
while (true) {
ScheduledTask s = tasks.peek();
if (s == null || s.dueMillis > now) break;
due.add(tasks.remove());
}
}
if (LOG.isLoggable(INFO)) {
LOG.info("Running " + due.size() + " due tasks");
}
for (ScheduledTask s : due) {
if (LOG.isLoggable(INFO)) {
LOG.info("Task is " + (now - s.dueMillis) + " ms overdue");
}
s.run();
}
}
private void scheduleAlarm() {
if (SDK_INT >= 23) scheduleIdleAlarm();
else scheduleInexactRepeatingAlarm();
}
private void rescheduleAlarm() {
// If SDK_INT < 23 the alarm repeats automatically
if (SDK_INT >= 23) scheduleIdleAlarm();
}
private void cancelAlarm() {
alarmManager.cancel(getAlarmPendingIntent());
}
private void scheduleInexactRepeatingAlarm() {
alarmManager.setInexactRepeating(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + ALARM_MS, ALARM_MS,
getAlarmPendingIntent());
}
@TargetApi(23)
private void scheduleIdleAlarm() {
alarmManager.setAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + ALARM_MS,
getAlarmPendingIntent());
}
private PendingIntent getAlarmPendingIntent() {
Intent i = new Intent(app, AlarmReceiver.class);
i.putExtra(EXTRA_PID, android.os.Process.myPid());
return PendingIntent.getBroadcast(app, REQUEST_ALARM, i,
FLAG_CANCEL_CURRENT);
}
private class ScheduledTask
implements Runnable, Cancellable, Comparable<ScheduledTask> {
private final Runnable task;
private final long dueMillis;
private final Future<?> check;
private final AtomicBoolean cancelled;
public ScheduledTask(Runnable task, long dueMillis,
Future<?> check, AtomicBoolean cancelled) {
this.task = task;
this.dueMillis = dueMillis;
this.check = check;
this.cancelled = cancelled;
}
@Override
public void run() {
if (!cancelled.get()) task.run();
}
@Override
public void cancel() {
// Cancel any future executions of this task
cancelled.set(true);
// Cancel the scheduled check for due tasks
check.cancel(false);
// Remove the task from the queue
synchronized (lock) {
tasks.remove(this);
}
}
@Override
public int compareTo(ScheduledTask s) {
//noinspection UseCompareMethod
if (dueMillis < s.dueMillis) return -1;
if (dueMillis > s.dueMillis) return 1;
return 0;
}
}
}
@@ -0,0 +1,49 @@
package org.briarproject.bramble.system;
import android.app.Application;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.AlarmListener;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AndroidTaskSchedulerModule {
public static class EagerSingletons {
@Inject
AndroidTaskScheduler scheduler;
}
@Provides
@Singleton
AndroidTaskScheduler provideAndroidTaskScheduler(
LifecycleManager lifecycleManager, Application app,
AndroidWakeLockManager wakeLockManager,
ScheduledExecutorService scheduledExecutorService) {
AndroidTaskScheduler scheduler = new AndroidTaskScheduler(app,
wakeLockManager, scheduledExecutorService);
lifecycleManager.registerService(scheduler);
return scheduler;
}
@Provides
@Singleton
AlarmListener provideAlarmListener(AndroidTaskScheduler scheduler) {
return scheduler;
}
@Provides
@Singleton
TaskScheduler provideTaskScheduler(AndroidTaskScheduler scheduler) {
return scheduler;
}
}
@@ -0,0 +1,74 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidWakeLock;
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");
}
}
}
}
}
@@ -0,0 +1,119 @@
package org.briarproject.bramble.system;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.PowerManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidWakeLock;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
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.bramble.api.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();
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
}
return ctx.getPackageName();
}
}
@@ -0,0 +1,23 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
@Module
public
class AndroidWakefulIoExecutorModule {
@Provides
@WakefulIoExecutor
Executor provideWakefulIoExecutor(@IoExecutor Executor ioExecutor,
AndroidWakeLockManager wakeLockManager) {
return r -> wakeLockManager.executeWakefully(r, ioExecutor,
"WakefulIoExecutor");
}
}
@@ -0,0 +1,130 @@
package org.briarproject.bramble.system;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import org.briarproject.bramble.api.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.bramble.api.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");
}
}
}
}
@@ -0,0 +1,22 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidWakeLock;
@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();
}
@@ -71,7 +71,7 @@ public class AndroidUtils {
return new Pair<>("", "");
}
private static boolean isValidBluetoothAddress(@Nullable String address) {
public static boolean isValidBluetoothAddress(@Nullable String address) {
return !StringUtils.isNullOrEmpty(address)
&& BluetoothAdapter.checkBluetoothAddress(address)
&& !address.equals(FAKE_BLUETOOTH_ADDRESS);
@@ -1,100 +0,0 @@
package org.briarproject.bramble.util;
import android.os.PowerManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
@ThreadSafe
@NotNullByDefault
public class RenewableWakeLock {
private static final Logger LOG =
Logger.getLogger(RenewableWakeLock.class.getName());
/**
* Automatically release the lock this many milliseconds after it's due
* to have been replaced and released.
*/
private static final int SAFETY_MARGIN_MS = 10_000;
private final PowerManager powerManager;
private final ScheduledExecutorService scheduler;
private final int levelAndFlags;
private final String tag;
private final long durationMs;
private final Runnable renewTask;
private final Object lock = new Object();
@Nullable
private PowerManager.WakeLock wakeLock; // Locking: lock
@Nullable
private ScheduledFuture future; // Locking: lock
public RenewableWakeLock(PowerManager powerManager,
ScheduledExecutorService scheduler, int levelAndFlags, String tag,
long duration, TimeUnit timeUnit) {
this.powerManager = powerManager;
this.scheduler = scheduler;
this.levelAndFlags = levelAndFlags;
this.tag = tag;
durationMs = MILLISECONDS.convert(duration, timeUnit);
renewTask = this::renew;
}
public void acquire() {
if (LOG.isLoggable(INFO)) LOG.info("Acquiring wake lock " + tag);
synchronized (lock) {
if (wakeLock != null) {
LOG.info("Already acquired");
return;
}
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
wakeLock.setReferenceCounted(false);
wakeLock.acquire(durationMs + SAFETY_MARGIN_MS);
future = scheduler.schedule(renewTask, durationMs, MILLISECONDS);
}
}
private void renew() {
if (LOG.isLoggable(INFO)) LOG.info("Renewing wake lock " + tag);
synchronized (lock) {
if (wakeLock == null) {
LOG.info("Already released");
return;
}
PowerManager.WakeLock oldWakeLock = wakeLock;
wakeLock = powerManager.newWakeLock(levelAndFlags, tag);
wakeLock.setReferenceCounted(false);
wakeLock.acquire(durationMs + SAFETY_MARGIN_MS);
oldWakeLock.release();
future = scheduler.schedule(renewTask, durationMs, MILLISECONDS);
}
}
public void release() {
if (LOG.isLoggable(INFO)) LOG.info("Releasing wake lock " + tag);
synchronized (lock) {
if (wakeLock == null) {
LOG.info("Already released");
return;
}
if (future == null) throw new AssertionError();
future.cancel(false);
future = null;
wakeLock.release();
wakeLock = null;
}
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ dependencyVerification {
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.9:tor-android-0.3.5.9.zip:853b0440feccd6904bd03e6b2de53a62ebcde1d58068beeadc447a7dff950bc8',
'org.briarproject:tor-android:0.3.5.10:tor-android-0.3.5.10.zip:edd83bf557fcff2105eaa0bdb3f607a6852ebe7360920929ae3039dd5f4774c5',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -13,7 +14,8 @@ public interface AccountManager {
* Returns true if the manager has the database key. This will be false
* before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and true after {@link #createAccount(String, String)}
* or {@link #signIn(String)} has returned true, until the process exits.
* or {@link #signIn(String)} has returned true, until
* {@link #deleteAccount()} is called or the process exits.
*/
boolean hasDatabaseKey();
@@ -22,25 +24,22 @@ public interface AccountManager {
* before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and non-null after
* {@link #createAccount(String, String)} or {@link #signIn(String)} has
* returned true, until the process exits.
* returned true, until {@link #deleteAccount()} is called or the process
* exits.
*/
@Nullable
SecretKey getDatabaseKey();
/**
* Returns true if the encrypted database key can be loaded from disk, and
* the database directory exists and is a directory.
* Returns true if the encrypted database key can be loaded from disk.
*/
boolean accountExists();
/**
* Creates an identity with the given name and registers it with the
* {@link IdentityManager}. Creates a database key, encrypts it with the
* given password and stores it on disk.
* <p/>
* This method does not create the database directory, so
* {@link #accountExists()} will continue to return false until the
* database directory is created.
* given password and stores it on disk. {@link #accountExists()} will
* return true after this method returns true.
*/
boolean createAccount(String name, String password);
@@ -54,17 +53,19 @@ public interface AccountManager {
* Loads the encrypted database key from disk and decrypts it with the
* given password.
*
* @return true if the database key was successfully loaded and decrypted.
* @throws DecryptionException If the database key could not be loaded and
* decrypted.
*/
boolean signIn(String password);
void signIn(String password) throws DecryptionException;
/**
* Loads the encrypted database key from disk, decrypts it with the old
* password, encrypts it with the new password, and stores it on disk,
* replacing the old key.
*
* @return true if the database key was successfully loaded, re-encrypted
* and stored.
* @throws DecryptionException If the database key could not be loaded and
* decrypted.
*/
boolean changePassword(String oldPassword, String newPassword);
void changePassword(String oldPassword, String newPassword)
throws DecryptionException;
}
@@ -35,24 +35,24 @@ public interface ClientHelper {
Message createMessageForStoringMetadata(GroupId g);
Message getMessage(MessageId m) throws DbException;
Message getSmallMessage(MessageId m) throws DbException;
Message getMessage(Transaction txn, MessageId m) throws DbException;
Message getSmallMessage(Transaction txn, MessageId m) throws DbException;
BdfList getMessageAsList(MessageId m) throws DbException, FormatException;
BdfList getSmallMessageAsList(MessageId m)
throws DbException, FormatException;
BdfList getMessageAsList(Transaction txn, MessageId m) throws DbException,
FormatException;
BdfList getSmallMessageAsList(Transaction txn, MessageId m)
throws DbException, FormatException;
BdfDictionary getGroupMetadataAsDictionary(GroupId g) throws DbException,
FormatException;
BdfDictionary getGroupMetadataAsDictionary(GroupId g)
throws DbException, FormatException;
BdfDictionary getGroupMetadataAsDictionary(Transaction txn, GroupId g)
throws DbException, FormatException;
BdfDictionary getMessageMetadataAsDictionary(MessageId m)
throws DbException,
FormatException;
throws DbException, FormatException;
BdfDictionary getMessageMetadataAsDictionary(Transaction txn, MessageId m)
throws DbException, FormatException;
@@ -67,8 +67,8 @@ public interface ClientHelper {
BdfDictionary query) throws DbException, FormatException;
Map<MessageId, BdfDictionary> getMessageMetadataAsDictionary(
Transaction txn, GroupId g, BdfDictionary query) throws DbException,
FormatException;
Transaction txn, GroupId g, BdfDictionary query)
throws DbException, FormatException;
void mergeGroupMetadata(GroupId g, BdfDictionary metadata)
throws DbException, FormatException;
@@ -1,8 +1,11 @@
package org.briarproject.bramble.api.plugin;
package org.briarproject.bramble.api.connection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
@@ -0,0 +1,130 @@
package org.briarproject.bramble.api.connection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.sync.Priority;
import java.util.Collection;
/**
* Keeps track of which contacts are currently connected by which transports.
*/
@NotNullByDefault
public interface ConnectionRegistry {
/**
* Registers an incoming connection from the given contact over the given
* transport. The connection's {@link Priority priority} can be set later
* via {@link #setPriority(ContactId, TransportId, InterruptibleConnection,
* Priority)} if a priority record is received from the contact.
* <p>
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ContactConnectedEvent} if this is the only connection with the
* contact.
*/
void registerIncomingConnection(ContactId c, TransportId t,
InterruptibleConnection conn);
/**
* Registers an outgoing connection to the given contact over the given
* transport.
* <p>
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ContactConnectedEvent} if this is the only connection with the
* contact.
* <p>
* If the registry has any "better" connections with the given contact, the
* given connection will be interrupted. If the registry has any "worse"
* connections with the given contact, those connections will be
* interrupted.
* <p>
* Connection A is considered "better" than connection B if both
* connections have had their priorities set, and either A's transport is
* {@link PluginConfig#getTransportPreferences() preferred} to B's, or
* they use the same transport and A has higher {@link Priority priority}
* than B.
* <p>
* For backward compatibility, connections without priorities are not
* considered better or worse than other connections.
*/
void registerOutgoingConnection(ContactId c, TransportId t,
InterruptibleConnection conn, Priority priority);
/**
* Unregisters a connection with the given contact over the given transport.
* <p>
* Broadcasts {@link ConnectionClosedEvent}. Also broadcasts
* {@link ContactDisconnectedEvent} if this is the only connection with
* the contact.
*/
void unregisterConnection(ContactId c, TransportId t,
InterruptibleConnection conn, boolean incoming, boolean exception);
/**
* Sets the {@link Priority priority} of a connection that was previously
* registered via {@link #registerIncomingConnection(ContactId, TransportId,
* InterruptibleConnection)}.
* <p>
* If the registry has any "better" connections with the given contact, the
* given connection will be interrupted. If the registry has any "worse"
* connections with the given contact, those connections will be
* interrupted.
* <p>
* Connection A is considered "better" than connection B if both
* connections have had their priorities set, and either A's transport is
* {@link PluginConfig#getTransportPreferences() preferred} to B's, or
* they use the same transport and A has higher {@link Priority priority}
* than B.
* <p>
* For backward compatibility, connections without priorities are not
* considered better or worse than other connections.
*/
void setPriority(ContactId c, TransportId t, InterruptibleConnection conn,
Priority priority);
/**
* Returns any contacts that are connected via the given transport.
*/
Collection<ContactId> getConnectedContacts(TransportId t);
/**
* Returns any contacts that are connected via the given transport or any
* {@link PluginConfig#getTransportPreferences() better} transport.
*/
Collection<ContactId> getConnectedOrBetterContacts(TransportId t);
/**
* Returns true if the given contact is connected via the given transport.
*/
boolean isConnected(ContactId c, TransportId t);
/**
* Returns true if the given contact is connected via any transport.
*/
boolean isConnected(ContactId c);
/**
* Registers a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionOpenedEvent} if this is the only connection
* with the pending contact.
*
* @return True if this is the only connection with the pending contact,
* false if it is redundant and should be closed
*/
boolean registerConnection(PendingContactId p);
/**
* Unregisters a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionClosedEvent}.
*/
void unregisterConnection(PendingContactId p, boolean success);
}
@@ -0,0 +1,19 @@
package org.briarproject.bramble.api.connection;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* A duplex sync connection that can be closed by interrupting its outgoing
* sync session.
*/
@NotNullByDefault
public interface InterruptibleConnection {
/**
* Interrupts the connection's outgoing sync session. If the underlying
* transport connection is alive and the remote peer is cooperative, this
* should result in both sync sessions ending and the connection being
* cleanly closed.
*/
void interruptOutgoingSession();
}
@@ -142,16 +142,17 @@ public interface CryptoComponent {
/**
* Decrypts and authenticates the given ciphertext that has been read from
* storage. The encryption and authentication keys are derived from the
* given password. Returns null if the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong).
* given password.
*
* @param keyStrengthener Used to strengthen the password-based key. If
* null, or if strengthening was not used when encrypting the ciphertext,
* the password-based key will not be strengthened
* @throws DecryptionException If the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong).
*/
@Nullable
byte[] decryptWithPassword(byte[] ciphertext, String password,
@Nullable KeyStrengthener keyStrengthener);
@Nullable KeyStrengthener keyStrengthener)
throws DecryptionException;
/**
* Returns true if the given ciphertext was encrypted using a strengthened
@@ -0,0 +1,17 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class DecryptionException extends Exception {
private final DecryptionResult result;
public DecryptionException(DecryptionResult result) {
this.result = result;
}
public DecryptionResult getDecryptionResult() {
return result;
}
}
@@ -0,0 +1,29 @@
package org.briarproject.bramble.api.crypto;
/**
* The result of a password-based decryption operation.
*/
public enum DecryptionResult {
/**
* Decryption succeeded.
*/
SUCCESS,
/**
* Decryption failed because the format of the ciphertext was invalid.
*/
INVALID_CIPHERTEXT,
/**
* Decryption failed because the {@link KeyStrengthener} used for
* encryption was not available for decryption.
*/
KEY_STRENGTHENER_ERROR,
/**
* Decryption failed because the password used for decryption did not match
* the password used for encryption.
*/
INVALID_PASSWORD
}
@@ -163,19 +163,23 @@ public interface DatabaseComponent extends TransactionManager {
* less than or equal to the given length, for transmission over a
* transport with the given maximum latency. Returns null if there are no
* sendable messages that fit in the given length.
*
* @param small True if only single-block messages should be sent
*/
@Nullable
Collection<Message> generateBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException;
int maxLength, int maxLatency, boolean small) throws DbException;
/**
* Returns an offer for the given contact for transmission over a
* transport with the given maximum latency, or null if there are no
* messages to offer.
*
* @param small True if only single-block messages should be offered
*/
@Nullable
Offer generateOffer(Transaction txn, ContactId c, int maxMessages,
int maxLatency) throws DbException;
int maxLatency, boolean small) throws DbException;
/**
* Returns a request for the given contact, or null if there are no
@@ -272,13 +276,14 @@ public interface DatabaseComponent extends TransactionManager {
Collection<Identity> getIdentities(Transaction txn) throws DbException;
/**
* Returns the message with the given ID.
* Returns the single-block message with the given ID.
* <p/>
* Read-only.
*
* @throws MessageDeletedException if the message has been deleted
* @throws MessageTooLargeException if the message has more than one block
*/
Message getMessage(Transaction txn, MessageId m) throws DbException;
Message getSmallMessage(Transaction txn, MessageId m) throws DbException;
/**
* Returns the IDs of all delivered messages in the given group.
@@ -0,0 +1,8 @@
package org.briarproject.bramble.api.db;
/**
* Thrown when a multi-block message is requested from the database via a
* method that is only suitable for requesting single-block messages.
*/
public class MessageTooLargeException extends DbException {
}
@@ -0,0 +1,19 @@
package org.briarproject.bramble.api.io;
import java.io.InputStream;
public interface TimeoutMonitor {
/**
* Returns an {@link InputStream} that wraps the given stream and allows
* read timeouts to be detected.
* <p>
* The returned stream must be {@link InputStream#close() closed} when it's
* no longer needed to ensure that resources held by the timeout monitor
* are released.
*
* @param timeoutMs The read timeout in milliseconds. Timeouts will be
* detected eventually but are not guaranteed to be detected immediately.
*/
InputStream createTimeoutInputStream(InputStream in, long timeoutMs);
}
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Wakeful;
import java.util.concurrent.ExecutorService;
@@ -65,6 +66,7 @@ public interface LifecycleManager {
* Opens the {@link DatabaseComponent} using the given key and starts any
* registered {@link Service Services}.
*/
@Wakeful
StartResult startServices(SecretKey dbKey);
/**
@@ -72,6 +74,7 @@ public interface LifecycleManager {
* registered {@link ExecutorService ExecutorServices}, and closes the
* {@link DatabaseComponent}.
*/
@Wakeful
void stopServices();
/**
@@ -104,6 +107,7 @@ public interface LifecycleManager {
*
* @param txn A read-write transaction
*/
@Wakeful
void onDatabaseOpened(Transaction txn) throws DbException;
}
}
@@ -1,16 +1,20 @@
package org.briarproject.bramble.api.lifecycle;
import org.briarproject.bramble.api.system.Wakeful;
public interface Service {
/**
* Starts the service. This method must not be called concurrently with
* {@link #stopService()}.
*/
@Wakeful
void startService() throws ServiceException;
/**
* Stops the service. This method must not be called concurrently with
* {@link #startService()}.
*/
@Wakeful
void stopService() throws ServiceException;
}
@@ -6,6 +6,16 @@ public interface BluetoothConstants {
int UUID_BYTES = 16;
// Transport properties
String PROP_ADDRESS = "address";
String PROP_UUID = "uuid";
// Local settings (not shared with contacts)
String PREF_ADDRESS_IS_REFLECTED = "addressIsReflected";
String PREF_EVER_CONNECTED = "everConnected";
// Default values for local settings
boolean DEFAULT_PREF_PLUGIN_ENABLE = false;
boolean DEFAULT_PREF_ADDRESS_IS_REFLECTED = false;
boolean DEFAULT_PREF_EVER_CONNECTED = false;
}
@@ -1,67 +0,0 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import java.util.Collection;
/**
* Keeps track of which contacts are currently connected by which transports.
*/
@NotNullByDefault
public interface ConnectionRegistry {
/**
* Registers a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ContactConnectedEvent} if this is the only connection with the
* contact.
*/
void registerConnection(ContactId c, TransportId t, boolean incoming);
/**
* Unregisters a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionClosedEvent}. Also broadcasts
* {@link ContactDisconnectedEvent} if this is the only connection with
* the contact.
*/
void unregisterConnection(ContactId c, TransportId t, boolean incoming);
/**
* Returns any contacts that are connected via the given transport.
*/
Collection<ContactId> getConnectedContacts(TransportId t);
/**
* Returns true if the given contact is connected via the given transport.
*/
boolean isConnected(ContactId c, TransportId t);
/**
* Returns true if the given contact is connected via any transport.
*/
boolean isConnected(ContactId c);
/**
* Registers a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionOpenedEvent} if this is the only connection
* with the pending contact.
*
* @return True if this is the only connection with the pending contact,
* false if it is redundant and should be closed
*/
boolean registerConnection(PendingContactId p);
/**
* Unregisters a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionClosedEvent}.
*/
void unregisterConnection(PendingContactId p, boolean success);
}
@@ -1,18 +0,0 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
/**
* An interface for handling peers discovered by transport plugins.
*/
@NotNullByDefault
public interface DiscoveryHandler {
/**
* Handles a peer discovered by a transport plugin.
*
* @param p A set of properties describing the discovered peer.
*/
void handleDevice(TransportProperties p);
}
@@ -7,7 +7,12 @@ public interface LanTcpConstants {
// Transport properties (shared with contacts)
String PROP_IP_PORTS = "ipPorts";
String PROP_PORT = "port";
String PROP_IPV6 = "ipv6";
// A local setting
// Local settings (not shared with contacts)
String PREF_LAN_IP_PORTS = "ipPorts";
String PREF_IPV6 = "ipv6";
// Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;
}
@@ -4,8 +4,9 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.Wakeful;
import java.util.List;
import java.util.Collection;
@NotNullByDefault
public interface Plugin {
@@ -70,11 +71,13 @@ public interface Plugin {
/**
* Starts the plugin.
*/
@Wakeful
void start() throws PluginException;
/**
* Stops the plugin.
*/
@Wakeful
void stop() throws PluginException;
/**
@@ -106,5 +109,7 @@ public interface Plugin {
* Attempts to create connections using the given transport properties,
* passing any created connections to the corresponding handlers.
*/
void poll(List<Pair<TransportProperties, ConnectionHandler>> properties);
@Wakeful
void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties);
}
@@ -8,6 +8,8 @@ import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import java.util.Collection;
/**
* An interface through which a transport plugin interacts with the rest of
* the application.
@@ -25,6 +27,11 @@ public interface PluginCallback extends ConnectionHandler {
*/
TransportProperties getLocalProperties();
/**
* Returns the plugin's remote transport properties.
*/
Collection<TransportProperties> getRemoteProperties();
/**
* Merges the given settings with the plugin's settings
*/
@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@NotNullByDefault
public interface PluginConfig {
@@ -14,4 +16,11 @@ public interface PluginConfig {
Collection<SimplexPluginFactory> getSimplexFactories();
boolean shouldPoll();
/**
* Returns a map representing transport preferences. For each entry in the
* map, connections via the transports identified by the value are
* preferred to connections via the transport identified by the key.
*/
Map<TransportId, List<TransportId>> getTransportPreferences();
}
@@ -1,9 +1,12 @@
package org.briarproject.bramble.api.plugin;
import static java.util.concurrent.TimeUnit.DAYS;
public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor");
// Transport properties
String PROP_ONION_V2 = "onion";
String PROP_ONION_V3 = "onion3";
@@ -13,17 +16,33 @@ public interface TorConstants {
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
// Local settings (not shared with contacts)
String PREF_TOR_NETWORK = "network2";
String PREF_TOR_PORT = "port";
String PREF_TOR_MOBILE = "useMobileData";
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
String HS_PRIVATE_KEY_V2 = "onionPrivKey";
String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
String HS_V3_CREATED = "onionPrivKey3Created";
/**
* How long to publish a v3 hidden service before retiring the v2 service.
*/
long V3_MIGRATION_PERIOD_MS = DAYS.toMillis(180);
// Values for PREF_TOR_NETWORK
int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3;
// Default values for local settings
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;
int DEFAULT_PREF_TOR_NETWORK = PREF_TOR_NETWORK_AUTOMATIC;
boolean DEFAULT_PREF_TOR_MOBILE = true;
boolean DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING = false;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.system;
package org.briarproject.bramble.api.plugin;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -11,15 +12,11 @@ import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting a scheduled executor service
* that can be used to schedule the execution of tasks.
* <p>
* The service should <b>only</b> be used for running tasks on other executors
* at scheduled times.
* No significant work should be run by the service itself!
* Annotation for injecting the {@link File directory} where the Tor plugin
* should store its state.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
public @interface Scheduler {
public @interface TorDirectory {
}
@@ -4,4 +4,7 @@ public interface WanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.wan");
// Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = false;
}
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
@@ -14,6 +15,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractDuplexTransportConnection
implements DuplexTransportConnection {
protected final TransportProperties remote = new TransportProperties();
private final Plugin plugin;
private final Reader reader;
private final Writer writer;
@@ -44,6 +47,11 @@ public abstract class AbstractDuplexTransportConnection
return writer;
}
@Override
public TransportProperties getRemoteProperties() {
return remote;
}
private class Reader implements TransportConnectionReader {
@Override
@@ -1,17 +1,14 @@
package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import java.util.List;
import org.briarproject.bramble.api.system.Wakeful;
import javax.annotation.Nullable;
@@ -25,6 +22,7 @@ public interface DuplexPlugin extends Plugin {
* Attempts to create and return a connection using the given transport
* properties. Returns null if a connection cannot be created.
*/
@Wakeful
@Nullable
DuplexTransportConnection createConnection(TransportProperties p);
@@ -62,16 +60,4 @@ public interface DuplexPlugin extends Plugin {
@Nullable
RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
boolean alice, ConnectionHandler incoming);
/**
* Returns true if the plugin supports peer discovery.
*/
boolean supportsDiscovery();
/**
* Attempts to discover peers using the given transport properties, passing
* any discovered peers to the corresponding handlers.
*/
void discoverPeers(List<Pair<TransportProperties, DiscoveryHandler>>
properties);
}
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
/**
* An interface for reading and writing data over a duplex transport. The
@@ -23,4 +24,10 @@ public interface DuplexTransportConnection {
* for writing to the connection.
*/
TransportConnectionWriter getWriter();
/**
* Returns a possibly empty set of {@link TransportProperties} describing
* the remote peer.
*/
TransportProperties getRemoteProperties();
}
@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that informs the Bluetooth plugin that we have enabled the
* Bluetooth adapter.
*/
@Immutable
@NotNullByDefault
public class BluetoothEnabledEvent extends Event {
}
@@ -13,13 +13,14 @@ public class ConnectionClosedEvent extends Event {
private final ContactId contactId;
private final TransportId transportId;
private final boolean incoming;
private final boolean incoming, exception;
public ConnectionClosedEvent(ContactId contactId, TransportId transportId,
boolean incoming) {
boolean incoming, boolean exception) {
this.contactId = contactId;
this.transportId = transportId;
this.incoming = incoming;
this.exception = exception;
}
public ContactId getContactId() {
@@ -33,4 +34,8 @@ public class ConnectionClosedEvent extends Event {
public boolean isIncoming() {
return incoming;
}
public boolean isException() {
return exception;
}
}
@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that asks the Bluetooth plugin to disable the Bluetooth adapter if
* we previously enabled it.
*/
@Immutable
@NotNullByDefault
public class DisableBluetoothEvent extends Event {
}
@@ -1,14 +0,0 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
*/
@Immutable
@NotNullByDefault
public class EnableBluetoothEvent extends Event {
}
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.system.Wakeful;
import javax.annotation.Nullable;
@@ -18,6 +19,7 @@ public interface SimplexPlugin extends Plugin {
* Attempts to create and return a reader for the given transport
* properties. Returns null if a reader cannot be created.
*/
@Wakeful
@Nullable
TransportConnectionReader createReader(TransportProperties p);
@@ -25,6 +27,7 @@ public interface SimplexPlugin extends Plugin {
* Attempts to create and return a writer for the given transport
* properties. Returns null if a writer cannot be created.
*/
@Wakeful
@Nullable
TransportConnectionWriter createWriter(TransportProperties p);
}
@@ -11,4 +11,33 @@ public interface TransportPropertyConstants {
* The maximum length of a property's key or value in UTF-8 bytes.
*/
int MAX_PROPERTY_LENGTH = 100;
/**
* Prefix for keys that represent reflected properties.
*/
String REFLECTED_PROPERTY_PREFIX = "u:";
/**
* Message metadata key for the transport ID of a local or remote update,
* as a BDF string.
*/
String MSG_KEY_TRANSPORT_ID = "transportId";
/**
* Message metadata key for the version number of a local or remote update,
* as a BDF long.
*/
String MSG_KEY_VERSION = "version";
/**
* Message metadata key for whether an update is local or remote, as a BDF
* boolean.
*/
String MSG_KEY_LOCAL = "local";
/**
* Group metadata key for any discovered transport properties of the
* contact, as a BDF dictionary.
*/
String GROUP_KEY_DISCOVERED = "discovered";
}
@@ -34,6 +34,14 @@ public interface TransportPropertyManager {
void addRemoteProperties(Transaction txn, ContactId c,
Map<TransportId, TransportProperties> props) throws DbException;
/**
* Stores the given properties discovered from an incoming transport
* connection. They will be overridden by any properties received while
* adding the contact or synced from the contact.
*/
void addRemotePropertiesFromConnection(ContactId c, TransportId t,
TransportProperties props) throws DbException;
/**
* Returns the local transport properties for all transports.
*/
@@ -0,0 +1,27 @@
package org.briarproject.bramble.api.properties.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when {@link TransportProperties} are received
* from a contact.
*/
@Immutable
@NotNullByDefault
public class RemoteTransportPropertiesUpdatedEvent extends Event {
private final TransportId transportId;
public RemoteTransportPropertiesUpdatedEvent(TransportId transportId) {
this.transportId = transportId;
}
public TransportId getTransportId() {
return transportId;
}
}
@@ -0,0 +1,23 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* A record containing a nonce for choosing between redundant sessions.
*/
@Immutable
@NotNullByDefault
public class Priority {
private final byte[] nonce;
public Priority(byte[] nonce) {
this.nonce = nonce;
}
public byte[] getNonce() {
return nonce;
}
}
@@ -0,0 +1,13 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* An interface for handling a {@link Priority} record received by an
* incoming {@link SyncSession}.
*/
@NotNullByDefault
public interface PriorityHandler {
void handle(Priority p);
}
@@ -10,4 +10,5 @@ public interface RecordTypes {
byte OFFER = 2;
byte REQUEST = 3;
byte VERSIONS = 4;
byte PRIORITY = 5;
}
@@ -29,10 +29,15 @@ public interface SyncConstants {
*/
int MESSAGE_HEADER_LENGTH = UniqueId.LENGTH + 8;
/**
* The maximum length of a block in bytes.
*/
int MAX_BLOCK_LENGTH = 32 * 1024; // 32 KiB
/**
* The maximum length of a message body in bytes.
*/
int MAX_MESSAGE_BODY_LENGTH = 32 * 1024; // 32 KiB
int MAX_MESSAGE_BODY_LENGTH = MAX_BLOCK_LENGTH;
/**
* The maximum length of a message in bytes.
@@ -49,4 +54,10 @@ public interface SyncConstants {
* simultaneously.
*/
int MAX_SUPPORTED_VERSIONS = 10;
/**
* The length of the priority nonce used for choosing between redundant
* connections.
*/
int PRIORITY_NONCE_BYTES = 16;
}
@@ -28,4 +28,8 @@ public interface SyncRecordReader {
boolean hasVersions() throws IOException;
Versions readVersions() throws IOException;
boolean hasPriority() throws IOException;
Priority readPriority() throws IOException;
}
@@ -17,5 +17,7 @@ public interface SyncRecordWriter {
void writeVersions(Versions v) throws IOException;
void writePriority(Priority p) throws IOException;
void flush() throws IOException;
}
@@ -2,18 +2,23 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.InputStream;
import javax.annotation.Nullable;
@NotNullByDefault
public interface SyncSessionFactory {
SyncSession createIncomingSession(ContactId c, InputStream in);
SyncSession createIncomingSession(ContactId c, InputStream in,
PriorityHandler handler);
SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency,
StreamWriter streamWriter);
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority);
}
@@ -0,0 +1,26 @@
package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when all sync connections using a given
* transport should be closed.
*/
@Immutable
@NotNullByDefault
public class CloseSyncConnectionsEvent extends Event {
private final TransportId transportId;
public CloseSyncConnectionsEvent(TransportId transportId) {
this.transportId = transportId;
}
public TransportId getTransportId() {
return transportId;
}
}
@@ -0,0 +1,43 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* A service that can be used to schedule the execution of tasks.
*/
@NotNullByDefault
public interface TaskScheduler {
/**
* Submits the given task to the given executor after the given delay.
* <p>
* If the platform supports wake locks, a wake lock will be held while
* submitting and running the task.
*/
Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit);
/**
* Submits the given task to the given executor after the given delay,
* and then repeatedly with the given interval between executions
* (measured from the end of one execution to the beginning of the next).
* <p>
* If the platform supports wake locks, a wake lock will be held while
* submitting and running the task.
*/
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit);
interface Cancellable {
/**
* Cancels the task if it has not already started running. If the task
* is {@link #scheduleWithFixedDelay(Runnable, Executor, long, long, TimeUnit) periodic},
* all future executions of the task are cancelled.
*/
void cancel();
}
}
@@ -0,0 +1,19 @@
package org.briarproject.bramble.api.system;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for methods that must be called while holding a wake lock, if
* the platform supports wake locks.
*/
@Qualifier
@Target(METHOD)
@Retention(RUNTIME)
public @interface Wakeful {
}
@@ -0,0 +1,26 @@
package org.briarproject.bramble.api.system;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the executor for long-running IO tasks that should
* run without sleeping. Also used for annotating methods that should run on
* this executor.
* <p>
* The contract of this executor is that tasks may be run concurrently, and
* submitting a task will never block. Tasks must not run indefinitely. Tasks
* submitted during shutdown are discarded.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
public @interface WakefulIoExecutor {
}
@@ -117,4 +117,10 @@ public class IoUtils {
throw new IOException(e);
}
}
public static boolean isNonEmptyDirectory(File f) {
if (!f.isDirectory()) return false;
File[] children = f.listFiles();
return children != null && children.length > 0;
}
}
@@ -2,13 +2,17 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.net.Inet6Address;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import javax.annotation.Nullable;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.isValidMac;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@NotNullByDefault
public class PrivacyUtils {
@@ -19,7 +23,7 @@ public class PrivacyUtils {
@Nullable
public static String scrubMacAddress(@Nullable String address) {
if (address == null || address.length() == 0) return null;
if (isNullOrEmpty(address) || !isValidMac(address)) return address;
// this is a fake address we need to know about
if (address.equals("02:00:00:00:00:00")) return address;
// keep first and last octet of MAC address
@@ -27,39 +31,37 @@ public class PrivacyUtils {
+ address.substring(14, 17);
}
@Nullable
public static String scrubInetAddress(InetAddress address) {
// don't scrub link and site local addresses
if (address.isLinkLocalAddress() || address.isSiteLocalAddress())
return address.toString();
// completely scrub IPv6 addresses
if (address instanceof Inet6Address) return "[scrubbed]";
// keep first and last octet of IPv4 addresses
return scrubInetAddress(address.toString());
if (address instanceof Inet4Address) {
// Don't scrub local IPv4 addresses
if (address.isLoopbackAddress() || address.isLinkLocalAddress() ||
address.isSiteLocalAddress()) {
return address.getHostAddress();
}
// Keep first and last octet of non-local IPv4 addresses
return scrubIpv4Address(address.getAddress());
} else {
// Keep first and last octet of IPv6 addresses
return scrubIpv6Address(address.getAddress());
}
}
@Nullable
public static String scrubInetAddress(@Nullable String address) {
if (address == null) return null;
int firstDot = address.indexOf(".");
if (firstDot == -1) return "[scrubbed]";
String prefix = address.substring(0, firstDot + 1);
int lastDot = address.lastIndexOf(".");
String suffix = address.substring(lastDot, address.length());
return prefix + "[scrubbed]" + suffix;
private static String scrubIpv4Address(byte[] ipv4) {
return (ipv4[0] & 0xFF) + ".[scrubbed]." + (ipv4[3] & 0xFF);
}
private static String scrubIpv6Address(byte[] ipv6) {
String hex = toHexString(ipv6).toLowerCase();
return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
}
@Nullable
public static String scrubSocketAddress(InetSocketAddress address) {
InetAddress inetAddress = address.getAddress();
return scrubInetAddress(inetAddress);
return scrubInetAddress(address.getAddress());
}
@Nullable
public static String scrubSocketAddress(SocketAddress address) {
if (address instanceof InetSocketAddress)
return scrubSocketAddress((InetSocketAddress) address);
return scrubInetAddress(address.toString());
return "[scrubbed]";
}
}
@@ -9,7 +9,6 @@ import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule;
@@ -31,8 +30,6 @@ public interface BrambleCoreEagerSingletons {
void inject(RendezvousModule.EagerSingletons init);
void inject(SystemModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init);
void inject(ValidationModule.EagerSingletons init);
@@ -50,7 +47,6 @@ public interface BrambleCoreEagerSingletons {
c.inject(new RendezvousModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons());
c.inject(new ValidationModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons());
@@ -1,6 +1,7 @@
package org.briarproject.bramble;
import org.briarproject.bramble.client.ClientModule;
import org.briarproject.bramble.connection.ConnectionModule;
import org.briarproject.bramble.contact.ContactModule;
import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.crypto.CryptoModule;
@@ -9,6 +10,7 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.db.DatabaseModule;
import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule;
@@ -19,7 +21,7 @@ import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.settings.SettingsModule;
import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule;
@@ -27,6 +29,8 @@ import dagger.Module;
@Module(includes = {
ClientModule.class,
ClockModule.class,
ConnectionModule.class,
ContactModule.class,
CryptoModule.class,
CryptoExecutorModule.class,
@@ -35,6 +39,7 @@ import dagger.Module;
DatabaseExecutorModule.class,
EventModule.class,
IdentityModule.class,
IoModule.class,
KeyAgreementModule.class,
LifecycleModule.class,
PluginModule.class,
@@ -44,7 +49,6 @@ import dagger.Module;
RendezvousModule.class,
SettingsModule.class,
SyncModule.class,
SystemModule.class,
TransportModule.class,
ValidationModule.class,
VersioningModule.class
@@ -2,6 +2,7 @@ package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -17,6 +18,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -24,6 +26,7 @@ import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -95,7 +98,7 @@ class AccountManagerImpl implements AccountManager {
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
new FileInputStream(f), Charset.forName("UTF-8")));
String key = reader.readLine();
reader.close();
return key;
@@ -147,7 +150,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes("UTF-8"));
out.write(key.getBytes(Charset.forName("UTF-8")));
out.flush();
out.close();
}
@@ -155,8 +158,7 @@ class AccountManagerImpl implements AccountManager {
@Override
public boolean accountExists() {
synchronized (stateChangeLock) {
return loadEncryptedDatabaseKey() != null
&& databaseConfig.getDatabaseDirectory().isDirectory();
return loadEncryptedDatabaseKey() != null;
}
}
@@ -193,31 +195,24 @@ class AccountManagerImpl implements AccountManager {
}
@Override
public boolean signIn(String password) {
public void signIn(String password) throws DecryptionException {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(password);
if (key == null) return false;
databaseKey = key;
return true;
databaseKey = loadAndDecryptDatabaseKey(password);
}
}
@GuardedBy("stateChangeLock")
@Nullable
private SecretKey loadAndDecryptDatabaseKey(String password) {
private SecretKey loadAndDecryptDatabaseKey(String password)
throws DecryptionException {
String hex = loadEncryptedDatabaseKey();
if (hex == null) {
LOG.warning("Failed to load encrypted database key");
return null;
throw new DecryptionException(INVALID_CIPHERTEXT);
}
byte[] ciphertext = fromHexString(hex);
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
keyStrengthener);
if (plaintext == null) {
LOG.info("Failed to decrypt database key");
return null;
}
SecretKey key = new SecretKey(plaintext);
// If the DB key was encrypted with a weak key and a key strengthener
// is now available, re-encrypt the DB key with a strengthened key
@@ -230,10 +225,11 @@ class AccountManagerImpl implements AccountManager {
}
@Override
public boolean changePassword(String oldPassword, String newPassword) {
public void changePassword(String oldPassword, String newPassword)
throws DecryptionException {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
return key != null && encryptAndStoreDatabaseKey(key, newPassword);
encryptAndStoreDatabaseKey(key, newPassword);
}
}
}
@@ -116,25 +116,27 @@ class ClientHelperImpl implements ClientHelper {
}
@Override
public Message getMessage(MessageId m) throws DbException {
return db.transactionWithResult(true, txn -> getMessage(txn, m));
public Message getSmallMessage(MessageId m) throws DbException {
return db.transactionWithResult(true, txn -> getSmallMessage(txn, m));
}
@Override
public Message getMessage(Transaction txn, MessageId m) throws DbException {
return db.getMessage(txn, m);
public Message getSmallMessage(Transaction txn, MessageId m)
throws DbException {
return db.getSmallMessage(txn, m);
}
@Override
public BdfList getMessageAsList(MessageId m) throws DbException,
FormatException {
return db.transactionWithResult(true, txn -> getMessageAsList(txn, m));
}
@Override
public BdfList getMessageAsList(Transaction txn, MessageId m)
public BdfList getSmallMessageAsList(MessageId m)
throws DbException, FormatException {
return toList(db.getMessage(txn, m).getBody());
return db.transactionWithResult(true, txn ->
getSmallMessageAsList(txn, m));
}
@Override
public BdfList getSmallMessageAsList(Transaction txn, MessageId m)
throws DbException, FormatException {
return toList(db.getSmallMessage(txn, m).getBody());
}
@Override
@@ -0,0 +1,79 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.util.IoUtils.read;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
abstract class Connection {
protected static final Logger LOG = getLogger(Connection.class.getName());
final KeyManager keyManager;
final ConnectionRegistry connectionRegistry;
final StreamReaderFactory streamReaderFactory;
final StreamWriterFactory streamWriterFactory;
Connection(KeyManager keyManager, ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory) {
this.keyManager = keyManager;
this.connectionRegistry = connectionRegistry;
this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory;
}
@Nullable
StreamContext recogniseTag(TransportConnectionReader reader,
TransportId transportId) {
StreamContext ctx;
try {
byte[] tag = readTag(reader.getInputStream());
return keyManager.getStreamContext(transportId, tag);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
return null;
}
}
private byte[] readTag(InputStream in) throws IOException {
byte[] tag = new byte[TAG_LENGTH];
read(in, tag);
return tag;
}
void disposeOnError(TransportConnectionReader reader, boolean recognised) {
try {
reader.dispose(true, recognised);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
void disposeOnError(TransportConnectionWriter writer) {
try {
writer.dispose(true);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
}
@@ -0,0 +1,114 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class ConnectionManagerImpl implements ConnectionManager {
private final Executor ioExecutor;
private final KeyManager keyManager;
private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory;
private final SyncSessionFactory syncSessionFactory;
private final HandshakeManager handshakeManager;
private final ContactExchangeManager contactExchangeManager;
private final ConnectionRegistry connectionRegistry;
private final TransportPropertyManager transportPropertyManager;
private final SecureRandom secureRandom;
@Inject
ConnectionManagerImpl(@IoExecutor Executor ioExecutor,
KeyManager keyManager, StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionRegistry connectionRegistry,
TransportPropertyManager transportPropertyManager,
SecureRandom secureRandom) {
this.ioExecutor = ioExecutor;
this.keyManager = keyManager;
this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory;
this.syncSessionFactory = syncSessionFactory;
this.handshakeManager = handshakeManager;
this.contactExchangeManager = contactExchangeManager;
this.connectionRegistry = connectionRegistry;
this.transportPropertyManager = transportPropertyManager;
this.secureRandom = secureRandom;
}
@Override
public void manageIncomingConnection(TransportId t,
TransportConnectionReader r) {
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, t, r));
}
@Override
public void manageIncomingConnection(TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new IncomingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
t, d));
}
@Override
public void manageIncomingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new IncomingHandshakeConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
handshakeManager, contactExchangeManager, this, p, t, d));
}
@Override
public void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w));
}
@Override
public void manageOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new OutgoingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
secureRandom, c, t, d));
}
@Override
public void manageOutgoingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new OutgoingHandshakeConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
handshakeManager, contactExchangeManager, this, p, t, d));
}
}
@@ -0,0 +1,26 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class ConnectionModule {
@Provides
ConnectionManager provideConnectionManager(
ConnectionManagerImpl connectionManager) {
return connectionManager;
}
@Provides
@Singleton
ConnectionRegistry provideConnectionRegistry(
ConnectionRegistryImpl connectionRegistry) {
return connectionRegistry;
}
}
@@ -0,0 +1,283 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.sync.Priority;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
class ConnectionRegistryImpl implements ConnectionRegistry {
private static final Logger LOG =
getLogger(ConnectionRegistryImpl.class.getName());
private final EventBus eventBus;
private final Map<TransportId, List<TransportId>> transportPrefs;
private final Object lock = new Object();
@GuardedBy("lock")
private final Map<ContactId, List<ConnectionRecord>> contactConnections;
@GuardedBy("lock")
private final Set<PendingContactId> connectedPendingContacts;
@Inject
ConnectionRegistryImpl(EventBus eventBus, PluginConfig pluginConfig) {
this.eventBus = eventBus;
transportPrefs = pluginConfig.getTransportPreferences();
contactConnections = new HashMap<>();
connectedPendingContacts = new HashSet<>();
}
@Override
public void registerIncomingConnection(ContactId c, TransportId t,
InterruptibleConnection conn) {
registerConnection(c, t, conn, true);
}
@Override
public void registerOutgoingConnection(ContactId c, TransportId t,
InterruptibleConnection conn, Priority priority) {
registerConnection(c, t, conn, false);
setPriority(c, t, conn, priority);
}
private void registerConnection(ContactId c, TransportId t,
InterruptibleConnection conn, boolean incoming) {
if (LOG.isLoggable(INFO)) {
if (incoming) LOG.info("Incoming connection registered: " + t);
else LOG.info("Outgoing connection registered: " + t);
}
boolean firstConnection;
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null) {
recs = new ArrayList<>();
contactConnections.put(c, recs);
}
firstConnection = recs.isEmpty();
recs.add(new ConnectionRecord(t, conn));
}
eventBus.broadcast(new ConnectionOpenedEvent(c, t, incoming));
if (firstConnection) {
LOG.info("Contact connected");
eventBus.broadcast(new ContactConnectedEvent(c));
}
}
@Override
public void setPriority(ContactId c, TransportId t,
InterruptibleConnection conn, Priority priority) {
if (LOG.isLoggable(INFO)) LOG.info("Setting connection priority: " + t);
List<InterruptibleConnection> toInterrupt;
boolean interruptNewConnection = false;
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null) throw new IllegalArgumentException();
toInterrupt = new ArrayList<>(recs.size());
for (ConnectionRecord rec : recs) {
if (rec.conn == conn) {
// Store the priority of this connection
rec.priority = priority;
} else if (rec.priority != null) {
int compare = compareConnections(t, priority,
rec.transportId, rec.priority);
if (compare == -1) {
// The old connection is better than the new one
interruptNewConnection = true;
} else if (compare == 1 && !rec.interrupted) {
// The new connection is better than the old one
toInterrupt.add(rec.conn);
rec.interrupted = true;
}
}
}
}
if (interruptNewConnection) {
LOG.info("Interrupting new connection");
conn.interruptOutgoingSession();
}
for (InterruptibleConnection old : toInterrupt) {
LOG.info("Interrupting old connection");
old.interruptOutgoingSession();
}
}
private int compareConnections(TransportId tA, Priority pA, TransportId tB,
Priority pB) {
if (getBetterTransports(tA).contains(tB)) return -1;
if (getBetterTransports(tB).contains(tA)) return 1;
return tA.equals(tB) ? Bytes.compare(pA.getNonce(), pB.getNonce()) : 0;
}
private List<TransportId> getBetterTransports(TransportId t) {
List<TransportId> better = transportPrefs.get(t);
return better == null ? emptyList() : better;
}
@Override
public void unregisterConnection(ContactId c, TransportId t,
InterruptibleConnection conn, boolean incoming, boolean exception) {
if (LOG.isLoggable(INFO)) {
if (incoming) LOG.info("Incoming connection unregistered: " + t);
else LOG.info("Outgoing connection unregistered: " + t);
}
boolean lastConnection;
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null || !recs.remove(new ConnectionRecord(t, conn)))
throw new IllegalArgumentException();
lastConnection = recs.isEmpty();
}
eventBus.broadcast(
new ConnectionClosedEvent(c, t, incoming, exception));
if (lastConnection) {
LOG.info("Contact disconnected");
eventBus.broadcast(new ContactDisconnectedEvent(c));
}
}
@Override
public Collection<ContactId> getConnectedContacts(TransportId t) {
synchronized (lock) {
List<ContactId> contactIds = new ArrayList<>();
for (Entry<ContactId, List<ConnectionRecord>> e :
contactConnections.entrySet()) {
for (ConnectionRecord rec : e.getValue()) {
if (rec.transportId.equals(t)) {
contactIds.add(e.getKey());
break;
}
}
}
if (LOG.isLoggable(INFO)) {
LOG.info(contactIds.size() + " contacts connected: " + t);
}
return contactIds;
}
}
@Override
public Collection<ContactId> getConnectedOrBetterContacts(TransportId t) {
synchronized (lock) {
List<TransportId> better = getBetterTransports(t);
List<ContactId> contactIds = new ArrayList<>();
for (Entry<ContactId, List<ConnectionRecord>> e :
contactConnections.entrySet()) {
for (ConnectionRecord rec : e.getValue()) {
if (rec.transportId.equals(t) ||
better.contains(rec.transportId)) {
contactIds.add(e.getKey());
break;
}
}
}
if (LOG.isLoggable(INFO)) {
LOG.info(contactIds.size()
+ " contacts connected or better: " + t);
}
return contactIds;
}
}
@Override
public boolean isConnected(ContactId c, TransportId t) {
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null) return false;
for (ConnectionRecord rec : recs) {
if (rec.transportId.equals(t)) return true;
}
return false;
}
}
@Override
public boolean isConnected(ContactId c) {
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
return recs != null && !recs.isEmpty();
}
}
@Override
public boolean registerConnection(PendingContactId p) {
boolean added;
synchronized (lock) {
added = connectedPendingContacts.add(p);
}
if (added) eventBus.broadcast(new RendezvousConnectionOpenedEvent(p));
return added;
}
@Override
public void unregisterConnection(PendingContactId p, boolean success) {
synchronized (lock) {
if (!connectedPendingContacts.remove(p))
throw new IllegalArgumentException();
}
eventBus.broadcast(new RendezvousConnectionClosedEvent(p, success));
}
private static class ConnectionRecord {
private final TransportId transportId;
private final InterruptibleConnection conn;
@GuardedBy("lock")
@Nullable
private Priority priority = null;
@GuardedBy("lock")
private boolean interrupted = false;
private ConnectionRecord(TransportId transportId,
InterruptibleConnection conn) {
this.transportId = transportId;
this.conn = conn;
}
@Override
public boolean equals(Object o) {
if (o instanceof ConnectionRecord) {
return conn == ((ConnectionRecord) o).conn;
} else {
return false;
}
}
@Override
public int hashCode() {
return conn.hashCode();
}
}
}
@@ -0,0 +1,109 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@NotNullByDefault
abstract class DuplexSyncConnection extends SyncConnection
implements InterruptibleConnection {
final Executor ioExecutor;
final TransportId transportId;
final TransportConnectionReader reader;
final TransportConnectionWriter writer;
final TransportProperties remote;
private final Object interruptLock = new Object();
@GuardedBy("interruptLock")
@Nullable
private SyncSession outgoingSession = null;
@GuardedBy("interruptLock")
private boolean interruptWaiting = false;
@Override
public void interruptOutgoingSession() {
SyncSession out = null;
synchronized (interruptLock) {
if (outgoingSession == null) interruptWaiting = true;
else out = outgoingSession;
}
if (out != null) out.interrupt();
}
void setOutgoingSession(SyncSession outgoingSession) {
boolean interruptWasWaiting = false;
synchronized (interruptLock) {
this.outgoingSession = outgoingSession;
if (interruptWaiting) {
interruptWasWaiting = true;
interruptWaiting = false;
}
}
if (interruptWasWaiting) outgoingSession.interrupt();
}
DuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, TransportId transportId,
DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.ioExecutor = ioExecutor;
this.transportId = transportId;
reader = connection.getReader();
writer = connection.getWriter();
remote = connection.getRemoteProperties();
}
void onReadError(boolean recognised) {
disposeOnError(reader, recognised);
disposeOnError(writer);
interruptOutgoingSession();
}
void onWriteError() {
disposeOnError(reader, true);
disposeOnError(writer);
}
SyncSession createDuplexOutgoingSession(StreamContext ctx,
TransportConnectionWriter w, @Nullable Priority priority)
throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createDuplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), w.getMaxIdleTime(),
streamWriter, priority);
}
}
@@ -0,0 +1,72 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
abstract class HandshakeConnection extends Connection {
final HandshakeManager handshakeManager;
final ContactExchangeManager contactExchangeManager;
final ConnectionManager connectionManager;
final PendingContactId pendingContactId;
final TransportId transportId;
final DuplexTransportConnection connection;
final TransportConnectionReader reader;
final TransportConnectionWriter writer;
HandshakeConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionManager connectionManager,
PendingContactId pendingContactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory);
this.handshakeManager = handshakeManager;
this.contactExchangeManager = contactExchangeManager;
this.connectionManager = connectionManager;
this.pendingContactId = pendingContactId;
this.transportId = transportId;
this.connection = connection;
reader = connection.getReader();
writer = connection.getWriter();
}
@Nullable
StreamContext allocateStreamContext(PendingContactId pendingContactId,
TransportId transportId) {
try {
return keyManager.getStreamContext(pendingContactId, transportId);
} catch (DbException e) {
logException(LOG, WARNING, e);
return null;
}
}
void onError(boolean recognised) {
disposeOnError(reader, recognised);
disposeOnError(writer);
}
}
@@ -0,0 +1,107 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.util.concurrent.Executor;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
IncomingDuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, TransportId transportId,
DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
}
@Override
public void run() {
// Read and recognise the tag
StreamContext ctx = recogniseTag(reader, transportId);
if (ctx == null) {
LOG.info("Unrecognised tag");
onReadError(false);
return;
}
ContactId contactId = ctx.getContactId();
if (contactId == null) {
LOG.warning("Expected contact tag, got rendezvous tag");
onReadError(true);
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onReadError(true);
return;
}
connectionRegistry.registerIncomingConnection(contactId, transportId,
this);
// Start the outgoing session on another thread
ioExecutor.execute(() -> runOutgoingSession(contactId));
try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// Update the connection registry when we receive our priority
PriorityHandler handler = p -> connectionRegistry.setPriority(
contactId, transportId, this, p);
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
reader.dispose(false, true);
interruptOutgoingSession();
connectionRegistry.unregisterConnection(contactId, transportId,
this, true, false);
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
onReadError(true);
connectionRegistry.unregisterConnection(contactId, transportId,
this, true, true);
}
}
private void runOutgoingSession(ContactId contactId) {
// Allocate a stream context
StreamContext ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
try {
// Create and run the outgoing session
SyncSession out = createDuplexOutgoingSession(ctx, writer, null);
setOutgoingSession(out);
out.run();
writer.dispose(false);
} catch (IOException e) {
logException(LOG, WARNING, e);
onWriteError();
}
}
}
@@ -0,0 +1,93 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingHandshakeConnection extends HandshakeConnection
implements Runnable {
IncomingHandshakeConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionManager connectionManager,
PendingContactId pendingContactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, handshakeManager, contactExchangeManager,
connectionManager, pendingContactId, transportId, connection);
}
@Override
public void run() {
// Read and recognise the tag
StreamContext ctxIn = recogniseTag(reader, transportId);
if (ctxIn == null) {
LOG.info("Unrecognised tag");
onError(false);
return;
}
PendingContactId inPendingContactId = ctxIn.getPendingContactId();
if (inPendingContactId == null) {
LOG.warning("Expected rendezvous tag, got contact tag");
onError(true);
return;
}
// Allocate the outgoing stream context
StreamContext ctxOut =
allocateStreamContext(pendingContactId, transportId);
if (ctxOut == null) {
LOG.warning("Could not allocate stream context");
onError(true);
return;
}
// Close the connection if it's redundant
if (!connectionRegistry.registerConnection(pendingContactId)) {
LOG.info("Redundant rendezvous connection");
onError(true);
return;
}
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
// Flush the output stream to send the outgoing stream header
StreamWriter out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
HandshakeResult result =
handshakeManager.handshake(pendingContactId, in, out);
contactExchangeManager.exchangeContacts(pendingContactId,
connection, result.getMasterKey(), result.isAlice(), false);
connectionRegistry.unregisterConnection(pendingContactId, true);
// Reuse the connection as a transport connection
connectionManager.manageIncomingConnection(transportId, connection);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError(true);
connectionRegistry.unregisterConnection(pendingContactId, false);
}
}
}
@@ -0,0 +1,79 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
private final TransportId transportId;
private final TransportConnectionReader reader;
IncomingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
TransportId transportId, TransportConnectionReader reader) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.transportId = transportId;
this.reader = reader;
}
@Override
public void run() {
// Read and recognise the tag
StreamContext ctx = recogniseTag(reader, transportId);
if (ctx == null) {
LOG.info("Unrecognised tag");
onError(false);
return;
}
ContactId contactId = ctx.getContactId();
if (contactId == null) {
LOG.warning("Received rendezvous stream, expected contact");
onError(true);
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onError(true);
return;
}
try {
// We don't expect to receive a priority for this connection
PriorityHandler handler = p ->
LOG.info("Ignoring priority for simplex connection");
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
reader.dispose(false, true);
} catch (IOException e) {
logException(LOG, WARNING, e);
onError(true);
}
}
private void onError(boolean recognised) {
disposeOnError(reader, recognised);
}
}
@@ -0,0 +1,140 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.SyncConstants.PRIORITY_NONCE_BYTES;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class OutgoingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
private final SecureRandom secureRandom;
private final ContactId contactId;
OutgoingDuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, SecureRandom secureRandom, ContactId contactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
this.secureRandom = secureRandom;
this.contactId = contactId;
}
@Override
public void run() {
// Allocate a stream context
StreamContext ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Cannot use handshake mode stream context");
onWriteError();
return;
}
// Start the incoming session on another thread
Priority priority = generatePriority();
ioExecutor.execute(() -> runIncomingSession(priority));
try {
// Create and run the outgoing session
SyncSession out =
createDuplexOutgoingSession(ctx, writer, priority);
setOutgoingSession(out);
out.run();
writer.dispose(false);
} catch (IOException e) {
logException(LOG, WARNING, e);
onWriteError();
}
}
private void runIncomingSession(Priority priority) {
// Read and recognise the tag
StreamContext ctx = recogniseTag(reader, transportId);
// Unrecognised tags are suspicious in this case
if (ctx == null) {
LOG.warning("Unrecognised tag for returning stream");
onReadError();
return;
}
// Check that the stream comes from the expected contact
ContactId inContactId = ctx.getContactId();
if (inContactId == null) {
LOG.warning("Expected contact tag, got rendezvous tag");
onReadError();
return;
}
if (!contactId.equals(inContactId)) {
LOG.warning("Wrong contact ID for returning stream");
onReadError();
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onReadError();
return;
}
connectionRegistry.registerOutgoingConnection(contactId, transportId,
this, priority);
try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// We don't expect to receive a priority for this connection
PriorityHandler handler = p ->
LOG.info("Ignoring priority for outgoing connection");
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
reader.dispose(false, true);
interruptOutgoingSession();
connectionRegistry.unregisterConnection(contactId, transportId,
this, false, false);
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
onReadError();
connectionRegistry.unregisterConnection(contactId, transportId,
this, false, true);
}
}
private void onReadError() {
// 'Recognised' is always true for outgoing connections
onReadError(true);
}
private Priority generatePriority() {
byte[] nonce = new byte[PRIORITY_NONCE_BYTES];
secureRandom.nextBytes(nonce);
return new Priority(nonce);
}
}
@@ -0,0 +1,115 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class OutgoingHandshakeConnection extends HandshakeConnection
implements Runnable {
OutgoingHandshakeConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionManager connectionManager,
PendingContactId pendingContactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, handshakeManager, contactExchangeManager,
connectionManager, pendingContactId, transportId, connection);
}
@Override
public void run() {
// Allocate the outgoing stream context
StreamContext ctxOut =
allocateStreamContext(pendingContactId, transportId);
if (ctxOut == null) {
LOG.warning("Could not allocate stream context");
onError();
return;
}
// Flush the output stream to send the outgoing stream header
StreamWriter out;
try {
out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
} catch (IOException e) {
logException(LOG, WARNING, e);
onError();
return;
}
// Read and recognise the tag
StreamContext ctxIn = recogniseTag(reader, transportId);
// Unrecognised tags are suspicious in this case
if (ctxIn == null) {
LOG.warning("Unrecognised tag for returning stream");
onError();
return;
}
// Check that the stream comes from the expected pending contact
PendingContactId inPendingContactId = ctxIn.getPendingContactId();
if (inPendingContactId == null) {
LOG.warning("Expected rendezvous tag, got contact tag");
onError();
return;
}
if (!inPendingContactId.equals(pendingContactId)) {
LOG.warning("Wrong pending contact ID for returning stream");
onError();
return;
}
// Close the connection if it's redundant
if (!connectionRegistry.registerConnection(pendingContactId)) {
LOG.info("Redundant rendezvous connection");
onError();
return;
}
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
HandshakeResult result =
handshakeManager.handshake(pendingContactId, in, out);
Contact contact = contactExchangeManager.exchangeContacts(
pendingContactId, connection, result.getMasterKey(),
result.isAlice(), false);
connectionRegistry.unregisterConnection(pendingContactId, true);
// Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contact.getId(),
transportId, connection);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError();
connectionRegistry.unregisterConnection(pendingContactId, false);
}
}
private void onError() {
// 'Recognised' is always true for outgoing connections
onError(true);
}
}
@@ -0,0 +1,78 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
private final ContactId contactId;
private final TransportId transportId;
private final TransportConnectionWriter writer;
OutgoingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
ContactId contactId, TransportId transportId,
TransportConnectionWriter writer) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.contactId = contactId;
this.transportId = transportId;
this.writer = writer;
}
@Override
public void run() {
// Allocate a stream context
StreamContext ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onError();
return;
}
try {
// Create and run the outgoing session
createSimplexOutgoingSession(ctx, writer).run();
writer.dispose(false);
} catch (IOException e) {
logException(LOG, WARNING, e);
onError();
}
}
private void onError() {
disposeOnError(writer);
}
private SyncSession createSimplexOutgoingSession(StreamContext ctx,
TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), streamWriter);
}
}
@@ -0,0 +1,64 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class SyncConnection extends Connection {
final SyncSessionFactory syncSessionFactory;
final TransportPropertyManager transportPropertyManager;
SyncConnection(KeyManager keyManager, ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory);
this.syncSessionFactory = syncSessionFactory;
this.transportPropertyManager = transportPropertyManager;
}
@Nullable
StreamContext allocateStreamContext(ContactId contactId,
TransportId transportId) {
try {
return keyManager.getStreamContext(contactId, transportId);
} catch (DbException e) {
logException(LOG, WARNING, e);
return null;
}
}
SyncSession createIncomingSession(StreamContext ctx,
TransportConnectionReader r, PriorityHandler handler)
throws IOException {
InputStream streamReader = streamReaderFactory.createStreamReader(
r.getInputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory
.createIncomingSession(c, streamReader, handler);
}
}
@@ -7,6 +7,7 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
@@ -39,6 +40,9 @@ import static java.lang.System.arraycopy;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -359,16 +363,17 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
@Nullable
public byte[] decryptWithPassword(byte[] input, String password,
@Nullable KeyStrengthener keyStrengthener) {
@Nullable KeyStrengthener keyStrengthener)
throws DecryptionException {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes();
// The input contains the format version, salt, cost parameter, IV,
// ciphertext and MAC
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes)
return null; // Invalid input
+ STORAGE_IV_BYTES + macBytes) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
int inputOff = 0;
// Format version
byte formatVersion = input[inputOff];
@@ -376,7 +381,7 @@ class CryptoComponentImpl implements CryptoComponent {
// Check whether we support this format version
if (formatVersion != PBKDF_FORMAT_SCRYPT &&
formatVersion != PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
return null;
throw new DecryptionException(INVALID_CIPHERTEXT);
}
// Salt
byte[] salt = new byte[PBKDF_SALT_BYTES];
@@ -385,8 +390,9 @@ class CryptoComponentImpl implements CryptoComponent {
// Cost parameter
long cost = ByteUtils.readUint32(input, inputOff);
inputOff += INT_32_BYTES;
if (cost < 2 || cost > Integer.MAX_VALUE)
return null; // Invalid cost parameter
if (cost < 2 || cost > Integer.MAX_VALUE) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
// IV
byte[] iv = new byte[STORAGE_IV_BYTES];
arraycopy(input, inputOff, iv, 0, iv.length);
@@ -394,8 +400,10 @@ class CryptoComponentImpl implements CryptoComponent {
// Derive the decryption key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
if (formatVersion == PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
if (keyStrengthener == null || !keyStrengthener.isInitialised())
return null; // Can't derive the same strengthened key
if (keyStrengthener == null || !keyStrengthener.isInitialised()) {
// Can't derive the same strengthened key
throw new DecryptionException(KEY_STRENGTHENER_ERROR);
}
key = keyStrengthener.strengthenKey(key);
}
// Initialise the cipher
@@ -411,7 +419,7 @@ class CryptoComponentImpl implements CryptoComponent {
cipher.process(input, inputOff, inputLen, output, 0);
return output;
} catch (GeneralSecurityException e) {
return null; // Invalid ciphertext
throw new DecryptionException(INVALID_PASSWORD);
}
}
@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MessageDeletedException;
import org.briarproject.bramble.api.db.MessageTooLargeException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.identity.Author;
@@ -332,13 +333,14 @@ interface Database<T> {
Collection<Identity> getIdentities(T txn) throws DbException;
/**
* Returns the message with the given ID.
* Returns the single-block message with the given ID.
* <p/>
* Read-only.
*
* @throws MessageDeletedException if the message has been deleted
* @throws MessageTooLargeException if the message has more than one block
*/
Message getMessage(T txn, MessageId m) throws DbException;
Message getSmallMessage(T txn, MessageId m) throws DbException;
/**
* Returns the IDs and states of all dependencies of the given message.
@@ -456,6 +458,15 @@ interface Database<T> {
Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
int maxMessages, int maxLatency) throws DbException;
/**
* Returns the IDs of some single-block messages that are eligible to be
* offered to the given contact, up to the given number of messages.
* <p/>
* Read-only.
*/
Collection<MessageId> getSmallMessagesToOffer(T txn, ContactId c,
int maxMessages, int maxLatency) throws DbException;
/**
* Returns the IDs of some messages that are eligible to be requested from
* the given contact, up to the given number of messages.
@@ -474,6 +485,15 @@ interface Database<T> {
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
int maxLatency) throws DbException;
/**
* Returns the IDs of some single-block messages that are eligible to be
* sent to the given contact, up to the given total length.
* <p/>
* Read-only.
*/
Collection<MessageId> getSmallMessagesToSend(T txn, ContactId c,
int maxLength, int maxLatency) throws DbException;
/**
* Returns the IDs of any messages that need to be validated.
* <p/>
@@ -406,16 +406,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, int maxLength, int maxLatency) throws DbException {
ContactId c, int maxLength, int maxLatency, boolean small)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getMessagesToSend(txn, c, maxLength, maxLatency);
Collection<MessageId> ids;
if (small)
ids = db.getSmallMessagesToSend(txn, c, maxLength, maxLatency);
else ids = db.getMessagesToSend(txn, c, maxLength, maxLatency);
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
messages.add(db.getMessage(txn, m));
messages.add(db.getSmallMessage(txn, m));
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
@@ -427,13 +430,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable
@Override
public Offer generateOffer(Transaction transaction, ContactId c,
int maxMessages, int maxLatency) throws DbException {
int maxMessages, int maxLatency, boolean small) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
Collection<MessageId> ids;
if (small)
ids = db.getSmallMessagesToOffer(txn, c, maxMessages, maxLatency);
else ids = db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
if (ids.isEmpty()) return null;
for (MessageId m : ids)
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
@@ -448,8 +453,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageId> ids = db.getMessagesToRequest(txn, c,
maxMessages);
Collection<MessageId> ids =
db.getMessagesToRequest(txn, c, maxMessages);
if (ids.isEmpty()) return null;
db.removeOfferedMessages(txn, c, ids);
return new Request(ids);
@@ -467,7 +472,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
messages.add(db.getMessage(txn, m));
messages.add(db.getSmallMessage(txn, m));
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
@@ -559,12 +564,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public Message getMessage(Transaction transaction, MessageId m)
public Message getSmallMessage(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
return db.getMessage(txn, m);
return db.getSmallMessage(txn, m);
}
@Override

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