Compare commits

...

193 Commits

Author SHA1 Message Date
akwizgran
a438050e68 Merge branch '1081-share-app-via-wifi-hotspot' into offline-testing 2021-07-09 10:41:37 +01:00
Torsten Grote
4d0fe24722 Merge branch '1802-sync-via-removable-storage' into offline-testing
# Conflicts:
#	bramble-api/src/main/java/org/briarproject/bramble/api/FeatureFlags.java
#	bramble-core/build.gradle
#	bramble-core/src/test/java/org/briarproject/bramble/test/BrambleCoreIntegrationTestModule.java
#	bramble-core/witness.gradle
#	bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java
#	briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java
#	briar-android/src/main/java/org/briarproject/briar/android/AppModule.java
#	briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java
#	briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java
#	briar-android/src/main/res/values/strings.xml
#	briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessModule.kt
#	briar-headless/src/test/java/org/briarproject/briar/headless/HeadlessTestModule.kt
2021-07-07 15:36:08 -03:00
akwizgran
559b29e8b5 Merge branch '2080-improve-offline-sharing-texts' into '1081-share-app-via-wifi-hotspot'
Improve texts on offline hotspot for a better UX

See merge request briar/briar!1493
2021-07-07 10:59:40 +00:00
Torsten Grote
1b7b285862 Merge remote-tracking branch 'origin/1081-share-app-via-wifi-hotspot' into offline-testing 2021-07-06 17:15:23 -03:00
Torsten Grote
178810241f Merge branch '2031-increase-max-latency' into '1802-sync-via-removable-storage'
Increase max latency of removable drive plugin to 28 days

See merge request briar/briar!1503
2021-07-06 20:06:48 +00:00
Torsten Grote
61c601cb6d Merge branch '2103-check-transport-keys' into '1802-sync-via-removable-storage'
Check whether we have transport keys before trying to send data

See merge request briar/briar!1502
2021-07-06 19:56:24 +00:00
Torsten Grote
2002ad08ca Merge branch '2105-let-contacts-know-drive-not-supported' into '1802-sync-via-removable-storage'
Don't configure the removable drive plugin on API < 19

See merge request briar/briar!1501
2021-07-06 19:53:50 +00:00
akwizgran
d134a67ee9 Increase max latency of removable drive plugin to 28 days. 2021-07-06 17:33:00 +01:00
akwizgran
04cf8e16a9 Check whether we have transport keys before trying to send data. 2021-07-06 16:24:43 +01:00
akwizgran
227d345858 Don't configure the removable drive plugin on API < 19. 2021-07-06 16:13:48 +01:00
Torsten Grote
07ef73ab56 Merge branch '2102-clear-introduction-state' into '1802-sync-via-removable-storage'
Clear keys from session when moving to AWAIT_ACTIVATE state

See merge request briar/briar!1500
2021-07-06 12:26:05 +00:00
Torsten Grote
ea2b1ff4d8 Merge branch '2079-reject-old-timestamps' into '1802-sync-via-removable-storage'
Reject old timestamps when deriving rotation mode keys

See merge request briar/briar!1481
2021-07-06 12:22:42 +00:00
akwizgran
69fac86a0c Clear keys from session when moving to AWAIT_ACTIVATE state. 2021-07-05 18:02:22 +01:00
akwizgran
bd6b6c1cd6 Reject old timestamps when deriving rotation mode keys. 2021-07-05 16:35:32 +01:00
Torsten Grote
be6c868135 Merge branch '2101-dont-increment-attempt-twice' into '1081-share-app-via-wifi-hotspot'
Do not increment the attempt variable twice when requesting group info

See merge request briar/briar!1499
2021-07-05 11:44:51 +00:00
Torsten Grote
041a296666 Merge branch '2090-avoid-double-tap-on-start-sharing-button' into '1081-share-app-via-wifi-hotspot'
Do not allow the user to tap the start sharing button twice quickly

See merge request briar/briar!1495
2021-07-05 11:24:47 +00:00
Torsten Grote
00f03f6587 Merge branch '1981-upgrade-material-tap-target-prompt' into 'master'
Upgrade MaterialTapTargetPrompt library to fix NPE

Closes #1981

See merge request briar/briar!1498
2021-07-05 11:17:39 +00:00
Sebastian Kürten
276eeb1c20 Do not increment the attempt variable twice when requesting group info 2021-07-05 12:48:58 +02:00
akwizgran
c68bd699f1 Upgrade MaterialTapTargetPrompt library to fix NPE. 2021-07-05 10:06:53 +01:00
Sebastian Kürten
d46cfb757e Do not allow the user to tap the start sharing button twice quickly 2021-07-05 10:32:49 +02:00
Torsten Grote
d81c4e7982 Merge branch '2065-transfer-data-ui' into '1802-sync-via-removable-storage'
Implement UI of transfer data feature

See merge request briar/briar!1486
2021-07-02 18:07:57 +00:00
Torsten Grote
8c4d6ed5e4 Remove guidelines for percent based laout width 2021-07-02 14:53:10 -03:00
akwizgran
b21b319cb7 Use guidelines to set image sizes. 2021-07-02 14:53:10 -03:00
Torsten Grote
780f6e97b9 Check if the chosen contact supports removable drive transport
and show message if not
2021-07-02 14:53:10 -03:00
Torsten Grote
1756215183 Combine transfer data graphics to reduce layout complexity
and make scaling work better on smaller screens
2021-07-02 14:53:09 -03:00
Torsten Grote
7e3db6c6df Address review feedback for Transfer Data UI 2021-07-02 14:53:09 -03:00
Torsten Grote
1adf408ade Migrate all image file pickers to ActivityResultLauncher
startActivityForResult is deprecated and the new API is nicer. Also, we can use the same launcher types in various places.
2021-07-02 14:53:09 -03:00
Torsten Grote
d662ae49ee Try to force file chooser to show internal/external storage by default 2021-07-02 14:53:08 -03:00
Torsten Grote
7939c8b213 Calculate percentages for send progress bar 2021-07-02 14:53:08 -03:00
Torsten Grote
1899873da3 Remove manual initial state and oldTask state argument
The latter is now handled via a LiveEvent
2021-07-02 14:53:08 -03:00
Torsten Grote
5beffb21f1 Hide Transfer Data feature behind feature flag 2021-07-02 14:53:05 -03:00
Torsten Grote
0f9afda329 Check if there is data to send and show a message if not 2021-07-02 14:46:08 -03:00
Torsten Grote
c16663b530 Always inform new observers about current state 2021-07-02 14:46:07 -03:00
Torsten Grote
77767b45c9 Re-organize conversations overflow menu 2021-07-02 14:46:07 -03:00
Torsten Grote
79ae8fea8d Transfer Data UI 2021-07-02 14:46:06 -03:00
Daniel Lublin
7e3eb1201a Start of UI for transfer data feature 2021-07-02 14:46:04 -03:00
akwizgran
eb283d81c5 Merge branch '2069-transport-key-agreement-integration-tests' into '1802-sync-via-removable-storage'
Integration tests for transport key agreement client

See merge request briar/briar!1492
2021-07-02 11:11:06 +00:00
Torsten Grote
be3700d364 Remove FIXME in test since we won't fix it this way 2021-06-30 16:57:32 -03:00
Torsten Grote
ccec17f28a Also test that messages arrive and activate keys 2021-06-30 16:49:03 -03:00
Torsten Grote
e8428925ae Add two more tests to TransportKeyAgreementIntegrationTest 2021-06-30 16:49:03 -03:00
Torsten Grote
195123e669 Ensure that private key is not stored anymore 2021-06-30 16:49:02 -03:00
Torsten Grote
abe570e905 Add first integration test for TransportKeyAgreementManager 2021-06-30 16:49:02 -03:00
Torsten Grote
a93b1f18ac Refactor base of BriarIntegrationTest into BrambleIntegrationTest 2021-06-30 16:49:02 -03:00
Torsten Grote
e4bd6fdf95 Put FeatureFlags for tests into a TestFeatureFlagModule 2021-06-30 16:49:01 -03:00
Torsten Grote
793d81bd93 Merge branch '2093-inconsistency-when-navigating-back-to-intro' into '1081-share-app-via-wifi-hotspot'
Don't move to HotspotFragment on rotate when user navigated back to introduction

See merge request briar/briar!1494
2021-06-30 15:16:33 +00:00
Torsten Grote
be637cef65 Merge branch '2091-supported-property' into '1802-sync-via-removable-storage'
Add a transport property to signal support for removable drives

See merge request briar/briar!1496
2021-06-30 14:53:15 +00:00
Sebastian Kürten
9370062e41 Don't move to HotspotFragment on rotate when user navigated back to introduction 2021-06-30 12:07:30 +02:00
akwizgran
8c1f721015 Add method for checking whether contact supports transport. 2021-06-28 16:51:30 +01:00
akwizgran
22ea4ced0d Add transport property to indicate support for removable drives. 2021-06-28 16:51:30 +01:00
Sebastian Kürten
312d31b40e Improve texts on offline hotspot for a better UX 2021-06-28 15:09:40 +02:00
Torsten Grote
b15d42b0cd Merge branch '2087-fix-issue-when-hotspot-activity-and-viewmodel-get-destroyed' into '1081-share-app-via-wifi-hotspot'
Fix bug that occurs when HotspotActivity gets destroyed

See merge request briar/briar!1489
2021-06-25 19:52:56 +00:00
Torsten Grote
9274b8ef4a Merge branch '2084-aborted-introduction-sessions' into '1802-sync-via-removable-storage'
Allow aborted introduction sessions to be retried

See merge request briar/briar!1490
2021-06-23 16:05:31 +00:00
Torsten Grote
7ef4ea51b3 Merge branch '2061-check-clock-sanity-at-startup' into '1802-sync-via-removable-storage'
Check whether system clock is reasonable at startup

See merge request briar/briar!1491
2021-06-23 15:59:52 +00:00
akwizgran
e285f21d1c Check whether system clock is reasonable at startup. 2021-06-23 16:40:42 +01:00
Sebastian Kürten
160cef25af Fix bug that occurs when HotspotActivity gets destroyed
If HotspotActivity gets destroyed, so will be its viewmodel, resulting
in an undefined state when the activity gets created again. While the
fragments will be restored, the view model and hotspot/webserver state
will not. Fix this by resetting the UI to reflect the reset of hotspot
and webserver.
2021-06-23 17:23:59 +02:00
akwizgran
c0293a1327 Merge branch '2070-transport-key-agreement-validator-test' into '1802-sync-via-removable-storage'
Add unit test for transport key agreement validator

See merge request briar/briar!1488
2021-06-23 10:16:52 +00:00
Torsten Grote
035c639aa0 Add TransportKeyAgreementValidatorTest 2021-06-22 17:20:47 -03:00
Torsten Grote
29d31e79c3 Merge branch '2086-fix-margin-on-qr-cardview' into '1081-share-app-via-wifi-hotspot'
Fix background color of cardview for qr code

See merge request briar/briar!1487
2021-06-22 11:49:58 +00:00
Sebastian Kürten
7f7210becd Fix background color of cardview for qr code 2021-06-22 09:46:34 +02:00
akwizgran
ce74fcaab5 Store ID of message that triggered abort. 2021-06-21 16:22:51 +01:00
akwizgran
00407539d3 Bump version numbers for 1.3.5 release. 2021-06-18 11:17:35 +01:00
akwizgran
62014f4d01 Update translations. 2021-06-18 11:16:33 +01:00
Torsten Grote
d4c1e132f7 Merge branch '2077-anything-to-send' into '1802-sync-via-removable-storage'
Add DB method for checking whether there's anything to send

See merge request briar/briar!1485
2021-06-17 13:30:31 +00:00
akwizgran
e7dddda7e5 Merge branch 'tor-0.3.5.15' into 'master'
Upgrade Tor to 0.3.5.15

Closes #2075

See merge request briar/briar!1483
2021-06-17 12:21:01 +00:00
akwizgran
208ff6f3a5 Merge branch '2056-view-pager-crash' into 'master'
Migrate to ViewPager2 to fix weird ViewPager crash

Closes #2056

See merge request briar/briar!1484
2021-06-17 12:07:04 +00:00
akwizgran
4d31465191 Merge branch 'master' into 'tor-0.3.5.15'
# Conflicts:
#   bramble-java/build.gradle
2021-06-17 12:05:34 +00:00
akwizgran
6b976df6a8 Add RemovableDriveManager method. 2021-06-17 13:01:33 +01:00
Torsten Grote
3e4db3b9da Merge branch '2045-flexible-sync' into '1802-sync-via-removable-storage'
Make retransmissions in the sync protocol more flexible

See merge request briar/briar!1482
2021-06-16 17:40:25 +00:00
Torsten Grote
cf987427e0 Migrate to ViewPager2 to fix weird ViewPager crash 2021-06-16 12:44:04 -03:00
akwizgran
0bf59eec20 Add comment explaining second client versioning message. 2021-06-16 16:26:29 +01:00
akwizgran
9f828a2222 Add DB method for checking whether there's anything to send 2021-06-16 16:25:11 +01:00
akwizgran
b11b959fd3 Merge branch 'upgrade-libraries-1.3' into 'master'
Upgrade libraries after 1.3 release

See merge request briar/briar!1455
2021-06-16 13:52:04 +00:00
Torsten Grote
61f660ca1d Upgrade libraries before 1.3 release 2021-06-16 10:40:30 -03:00
Torsten Grote
7be77b8c60 Merge branch '2038-transport-key-agreement-client' into '1802-sync-via-removable-storage'
Add transport key agreement client

See merge request briar/briar!1474
2021-06-16 12:28:00 +00:00
akwizgran
d5853e8403 Add integration test for eager retransmission. 2021-06-16 12:29:49 +01:00
akwizgran
32e9bf01ec Update DB method that gets total size of messages to send. 2021-06-16 12:29:49 +01:00
akwizgran
a5ce400341 Use eager retransmission if the transport is lossy and cheap. 2021-06-16 12:29:49 +01:00
akwizgran
a960bfb2c1 Add tests for eager retransmission. 2021-06-16 12:29:49 +01:00
akwizgran
847650f280 Replace inner classes with lambdas. 2021-06-16 12:29:49 +01:00
akwizgran
77a3199aac Update SimplexOutgoingSession to support sending unacked messages. 2021-06-16 12:29:49 +01:00
akwizgran
9a58b37ce2 Add database methods for sending unacked messages. 2021-06-16 12:29:49 +01:00
akwizgran
6d44521016 Upgrade Tor to 0.3.5.15. 2021-06-16 11:16:10 +01:00
Torsten Grote
608e1eac6b Merge branch '2071-removable-drive-task-refactoring' into '1802-sync-via-removable-storage'
Refactor removable drive task management

See merge request briar/briar!1480
2021-06-15 12:23:27 +00:00
akwizgran
09de768e7e Merge branch '2038-key-manager-methods' into '1802-sync-via-removable-storage'
Key manager changes to support transport key agreement client

See merge request briar/briar!1473
2021-06-15 10:55:31 +00:00
akwizgran
faab80f0ea Hold lock while calling notifyObservers(). 2021-06-15 11:47:10 +01:00
akwizgran
07162cad8b Refactor removable drive tasks. 2021-06-15 11:44:10 +01:00
Torsten Grote
a5a1cdfabb Merge branch '2039-implement-hotspot-error-fragment' into '1081-share-app-via-wifi-hotspot'
Resolve "Implement HotspotErrorFragment"

See merge request briar/briar!1469
2021-06-14 20:23:18 +00:00
Sebastian Kürten
bfcb469d49 Use FragmentContainerView for displaying FallbackFragment 2021-06-14 17:14:50 +02:00
Sebastian Kürten
f8b645d2b1 Improve hotspot error fragment UI
* Use different highlighting for error message
* Improve margins in fragment_hotspot_save_apk.xml
* Address some review feedback
2021-06-14 17:14:35 +02:00
Sebastian Kürten
052eb03c9e Pass error message to feedback activity 2021-06-14 17:13:14 +02:00
Sebastian Kürten
83bf3f4ca7 Create FallbackFragment for alternative apk sharing method 2021-06-14 17:12:02 +02:00
Sebastian Kürten
f9181fa021 Log hotspot errors 2021-06-14 17:10:52 +02:00
Sebastian Kürten
79dae27c24 Wire feedback button to show feedback fragment 2021-06-14 17:10:46 +02:00
akwizgran
7289f5ba8a Merge branch '2059-no-such-group-exception' into 'master'
Check if group still exists before storing MessageId of thread position

Closes #2059

See merge request briar/briar!1477
2021-06-14 14:57:21 +00:00
Torsten Grote
b657c7f347 Ignore NoSuchGroupException when storing MessageId of thread position for removed group 2021-06-10 15:45:31 -03:00
akwizgran
ac0fc21e6e Merge branch '2038-db-methods' into '1802-sync-via-removable-storage'
New DB methods to support transport key agreement client

See merge request briar/briar!1472
2021-06-10 15:28:20 +00:00
akwizgran
dab736ce0e Merge branch '1802-defer-delivery' into '1802-sync-via-removable-storage'
Allow sync clients to defer delivery of messages

See merge request briar/briar!1471
2021-06-10 15:27:08 +00:00
Sebastian Kürten
8ef21637a9 Outline specific error fragment for hotspot 2021-06-09 18:53:59 +02:00
Sebastian Kürten
e3a1fca22e Let HotspotActivity implement BaseFragmentListener 2021-06-09 18:53:54 +02:00
Torsten Grote
ea9a2789ab Move hotspot help ActivityResultLauncher into method 2021-06-09 17:48:17 +02:00
Torsten Grote
cbdbd10cb3 Adapt hotspot buttons to latest design and add a nullability annotation 2021-06-09 17:48:17 +02:00
Torsten Grote
d6f985174a Make HotspotHelpFragment headlines bold 2021-06-09 17:48:17 +02:00
Torsten Grote
d184fbd3fe Handle returned Uri being null 2021-06-09 17:48:17 +02:00
Torsten Grote
ef623370b6 Save the APK as a hotspot fallback 2021-06-09 17:48:17 +02:00
Sebastian Kürten
5ac636d52d Add feature flag for sharing the app via offline hotspot 2021-06-09 17:48:17 +02:00
Sebastian Kürten
f1c71ec5a7 Recommend to undo settings to install apps from unknown sources 2021-06-09 17:48:16 +02:00
Torsten Grote
5cc280be61 Add missing hotspot nullability annotations 2021-06-09 17:48:16 +02:00
Torsten Grote
a5d8faef3c Move savedNetworkConfig into HotspotManager and use constructor injection 2021-06-09 17:48:16 +02:00
Torsten Grote
e22e9dcade Make hotspot SSID and passphrase persistent 2021-06-09 17:48:16 +02:00
Sebastian Kürten
7474ad8606 Use better filename for apk files shared via hotspot 2021-06-09 17:48:16 +02:00
Torsten Grote
1c3d90f7fc Show a snackbar when a peer connected to the hotspot 2021-06-09 17:48:16 +02:00
Torsten Grote
6f8d7167db Don't start hotspot while running and use proper ErrorFragment 2021-06-09 17:48:16 +02:00
Torsten Grote
99da50d37c Port code from Offline hotspot test app 2021-06-09 17:48:15 +02:00
Torsten Grote
15f5c8deee Fix hotspot notification on old APIs 2021-06-09 17:48:15 +02:00
Torsten Grote
7913cd322e Rename tab fragments
and remove redundant NonNull annotations
2021-06-09 17:48:15 +02:00
Torsten Grote
de8ad8f6f9 Show notification while hotspot is active 2021-06-09 17:48:15 +02:00
Torsten Grote
d0bc17e634 Add hotspot troubleshooting info 2021-06-09 17:48:15 +02:00
Torsten Grote
85433611a5 Add offline sharing entry point to Settings/Actions 2021-06-09 17:48:15 +02:00
Torsten Grote
ebd5879761 Let info screens scroll in case of insufficient space 2021-06-09 17:48:15 +02:00
Torsten Grote
b255ab07ae Implement info screens for offline app sharing 2021-06-09 17:48:14 +02:00
Torsten Grote
a86ba50dec Implement intro screen for offline app sharing 2021-06-09 17:48:09 +02:00
Torsten Grote
40865425c5 Merge branch 'fix-padding-of-contact-exchange-error' into 'master'
Fix missing padding for title in contact exchange error fragment

See merge request briar/briar!1478
2021-06-09 11:56:57 +00:00
Sebastian Kürten
f7249f5e84 Fix missing padding for title in contact exchange error fragment 2021-06-09 13:45:32 +02:00
Torsten Grote
bcbc96dc2d Merge branch '2037-create-removabledriveviewmodel' into '1802-sync-via-removable-storage'
Add RemovableDriveViewModel

See merge request briar/briar!1475
2021-06-09 11:32:10 +00:00
akwizgran
a72e92de24 Timestamp isn't needed for deriving root key. 2021-06-09 10:08:07 +01:00
Daniel Lublin
1ddcd6cfff Make pkg private 2021-06-08 20:31:23 +02:00
akwizgran
5dfd9e3546 Make tests more readable. 2021-06-08 17:13:18 +01:00
akwizgran
e05575b956 Add unit tests for addRotationKeys() methods. 2021-06-08 15:51:29 +01:00
Daniel Lublin
fd810f5c16 Move to new removabledrive package 2021-06-08 12:25:09 +02:00
Daniel Lublin
3f5e131250 Use US locale for now 2021-06-08 12:18:33 +02:00
Daniel Lublin
3ee516599d Add initial RemovableDriveViewModel 2021-06-07 13:17:50 +02:00
akwizgran
c703d90636 Remove unused remote timestamp from session. 2021-06-01 14:50:14 +01:00
akwizgran
e228b9fcbf Add transport key agreement client. 2021-06-01 14:18:02 +01:00
akwizgran
6e6cadd3ad Refactor KeyManager startup so managers are created earlier. 2021-06-01 14:17:12 +01:00
akwizgran
9cc8d44778 Add a key manager method for adding a single set of transport keys. 2021-06-01 11:34:27 +01:00
akwizgran
ee6f571c31 Add a DB method for checking whether transport keys exist. 2021-06-01 11:34:26 +01:00
akwizgran
2ac3bdd3ae Add database method for getting transports with keys. 2021-06-01 11:34:26 +01:00
akwizgran
e35ffe0cf0 Add javadocs for message states. 2021-06-01 11:33:06 +01:00
akwizgran
8a04d8edc4 Allow sync clients to defer delivery of messages. 2021-06-01 11:24:55 +01:00
Torsten Grote
cc04fb05c2 Merge branch '1971-tor-0.3.5.14' into 'master'
Upgrade Tor to 0.3.5.14

Closes #1971

See merge request briar/briar!1470
2021-05-31 12:02:58 +00:00
akwizgran
58eb1d9e24 Upgrade Tor to 0.3.5.14. 2021-05-28 11:45:21 +01:00
akwizgran
76b7e6fecf Bump version numbers for 1.3.4 release. 2021-05-19 10:47:08 +01:00
akwizgran
980940a7cf Update translations. 2021-05-19 10:46:21 +01:00
akwizgran
9a021daae8 Merge branch '2043-blog-scrolling' into 'master'
Automatically scroll to our own blog posts

Closes #2043

See merge request briar/briar!1462
2021-05-19 09:35:10 +00:00
Torsten Grote
cc9904a454 Automatically scroll to our own blog posts 2021-05-18 15:39:22 -03:00
Torsten Grote
b0faab9395 Merge branch 'remove-failed-bridges' into 'master'
Remove three failed bridges

See merge request briar/briar!1460
2021-05-17 17:37:58 +00:00
akwizgran
f1198b47fd Remove three failed bridges. 2021-05-17 16:36:00 +01:00
akwizgran
37f2ab555f Update translations. 2021-05-17 16:26:22 +01:00
akwizgran
a5fb3bb4a4 Merge branch '2016-2017-2018-removable-drive-reader-writer' into '1802-sync-via-removable-storage'
Create removable drive manager and reader/writer tasks

See merge request briar/briar!1458
2021-05-11 14:01:53 +00:00
akwizgran
eae329cdfa Refactor manager and tasks to remove reliance on files. 2021-05-11 12:19:16 +01:00
akwizgran
0ce0551f0d Update progress of writer task. 2021-05-11 12:19:16 +01:00
akwizgran
a198e7d08e Ensure that observers see the final state even if they're added late. 2021-05-11 12:19:16 +01:00
akwizgran
bca6f1506e Add integration test for syncing via removable drives. 2021-05-11 12:19:16 +01:00
akwizgran
e420201b00 Implement RemovableDriveWriterTask, except for progress updates. 2021-05-11 12:19:16 +01:00
akwizgran
03248d04e5 Fix typo in class names. 2021-05-11 12:19:16 +01:00
akwizgran
2c39b02644 Implement RemovableDriverReaderTask. 2021-05-11 12:19:16 +01:00
akwizgran
c9c6f3682c Add task factory. 2021-05-11 12:19:16 +01:00
akwizgran
8f4a0ef030 Add removable drive manager with placeholder task implementations. 2021-05-11 12:19:14 +01:00
akwizgran
5fe22bcd57 Merge branch '2035-android-removable-drive-plugin' into '1802-sync-via-removable-storage'
Add Android implementation of RemovableDrivePlugin

See merge request briar/briar!1457
2021-05-11 11:13:14 +00:00
akwizgran
b4880af7e2 Add Android implementation of RemovableDrivePlugin. 2021-05-10 14:19:24 +01:00
akwizgran
51d21bd669 Decouple RemovableDrivePlugin from FileConstants. 2021-05-10 13:48:12 +01:00
Torsten Grote
b8f3728a0d Merge branch '2015-removable-drive-plugin' into '1802-sync-via-removable-storage'
Create RemovableDrivePlugin

See merge request briar/briar!1454
2021-05-10 12:47:21 +00:00
akwizgran
61ca2a391b Merge branch 'create-testdata-zero-values' into 'master'
Add button to zero values when creating test data

See merge request briar/briar!1447
2021-05-10 12:05:26 +00:00
akwizgran
bbfd4f137d Merge branch '2013-db-method-for-amount-of-data-to-sync' into '1802-sync-via-removable-storage'
Add DB method for getting amount of data to sync

See merge request briar/briar!1452
2021-05-10 12:00:11 +00:00
akwizgran
88537b9323 Merge branch '2032-connect-via-bt-crash' into 'master'
Remove duplicate code due to merge mistake

Closes #2032

See merge request briar/briar!1456
2021-05-10 11:55:38 +00:00
Daniel Lublin
e6c004e8f6 Add button to zero values when creating test data 2021-05-10 13:46:49 +02:00
Daniel Lublin
7e3ca76dd1 Merge branch '2014-messages-sent-event' into '1802-sync-via-removable-storage'
Update MessagesSentEvent to include amount of data sent

See merge request briar/briar!1453
2021-05-10 11:44:27 +00:00
akwizgran
dc7290dab7 Remove duplicate code due to merge mistake. 2021-05-10 12:29:28 +01:00
akwizgran
524c8d26f8 Don't inject default RemovableDrivePluginFactory on Android. 2021-05-07 17:48:39 +01:00
akwizgran
7eccf7dac1 Decouple removable drive plugin from java.io.File for portability. 2021-05-07 17:36:10 +01:00
akwizgran
0bc06248ed Clean up plugin injection code, remove unused module. 2021-05-06 16:59:45 +01:00
akwizgran
c999f05cc7 Configure removable drive plugin for Android. 2021-05-06 16:59:45 +01:00
akwizgran
428269b312 Add removable drive plugin. 2021-05-06 16:59:45 +01:00
akwizgran
588e05ce83 Update MessagesSentEvent to include amount of data sent. 2021-05-06 16:20:15 +01:00
akwizgran
9dff8bd64a Merge branch '2005-connect-via-bt-error' into 'master'
Connect via Bluetooth: Wait before showing an error Toast

See merge request briar/briar!1446
2021-05-06 13:14:32 +00:00
akwizgran
35c2f60129 Merge branch 'master' into '2005-connect-via-bt-error'
# Conflicts:
#   briar-android/src/main/java/org/briarproject/briar/android/conversation/BluetoothConnecter.java
2021-05-06 13:03:46 +00:00
akwizgran
471a2372c8 Merge branch '2027-pause-polling-when-doing-connect-via-bt' into 'master'
Disable polling while doing connect-via-BT

Closes #2027

See merge request briar/briar!1450
2021-05-06 12:59:38 +00:00
Daniel Lublin
fba028db03 Disable polling while doing connect-via-BT 2021-05-06 11:13:35 +02:00
akwizgran
f7875c99b6 Add DB method for getting amount of data to sync. 2021-05-05 17:52:37 +01:00
Torsten Grote
c647c52638 Merge branch '2009-xiaomi-setup-screen' into 'master'
Show instructions for locking Briar to the Xiaomi recent apps list

Closes #2009

See merge request briar/briar!1451
2021-05-05 16:39:55 +00:00
akwizgran
cad5edcf86 Move input state methods to UiUtils. 2021-05-05 16:57:25 +01:00
akwizgran
38f70bb6be Add full stop. 2021-05-05 13:02:56 +01:00
akwizgran
33bdc81b3e Show instructions for locking Briar to the Xiaomi recent apps list. 2021-05-05 12:37:23 +01:00
Torsten Grote
21fd7f5eed Merge branch 'allow-one-unreachable-bridge' into 'master'
Allow BridgeTest to pass if one bridge is unreachable

See merge request briar/briar!1449
2021-05-04 12:31:38 +00:00
akwizgran
6354e91b55 Allow BridgeTest to pass if one bridge is unreachable. 2021-05-04 13:13:57 +01:00
Torsten Grote
8123c06348 Merge branch '2012-update-bubbles-after-removing-messages' into 'master'
Update disappearing message bubbles after removing messages

Closes #2012

See merge request briar/briar!1448
2021-05-03 16:57:04 +00:00
akwizgran
663c648337 Update disappearing message bubbles after removing messages. 2021-05-03 15:16:11 +01:00
akwizgran
bee4e94987 Bump version numbers for 1.3.3 release. 2021-05-03 13:55:39 +01:00
akwizgran
c44bdc8762 Update translations. 2021-05-03 13:38:14 +01:00
Torsten Grote
423ecf71d8 Merge branch '1894-viewmodel-for-rssfeed-activities' into 'master'
Introduce ViewModel for RssFeed*Activity

Closes #1894

See merge request briar/briar!1366
2021-05-03 12:35:32 +00:00
Daniel Lublin
73c7882cc0 Introduce RssFeedViewModel
Furnishing the RssFeed function as a single activity with fragments for
Manage and Import.
2021-05-03 09:40:40 +02:00
Torsten Grote
552b9ef21a Wait for an incoming connection before showing an error Toast 2021-04-30 16:01:54 -03:00
Torsten Grote
683af1ec3a Merge branch '1827-target-api-30' into 'master'
Raise target API level to 30, upgrade build tools to 30.0.3

Closes #1827

See merge request briar/briar!1445
2021-04-30 17:44:49 +00:00
323 changed files with 12466 additions and 2069 deletions

View File

@@ -15,8 +15,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10302 versionCode 10305
versionName "1.3.2" versionName "1.3.5"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -42,18 +42,18 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.13@zip' tor 'org.briarproject:tor-android:0.3.5.15'
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip' tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'junit:junit:4.12' testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:2.8.2" testImplementation "org.jmock:jmock-legacy:$jmock_version"
} }
def torBinariesDir = 'src/main/res/raw' def torBinariesDir = 'src/main/res/raw'

View File

@@ -87,7 +87,7 @@ class AndroidBluetoothPlugin extends
Clock clock, Clock clock,
Backoff backoff, Backoff backoff,
PluginCallback callback, PluginCallback callback,
int maxLatency, long maxLatency,
int maxIdleTime) { int maxIdleTime) {
super(connectionLimiter, connectionFactory, ioExecutor, super(connectionLimiter, connectionFactory, ioExecutor,
wakefulIoExecutor, secureRandom, backoff, callback, wakefulIoExecutor, secureRandom, backoff, callback,

View File

@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
@Inject @Inject
public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor, AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
AndroidWakeLockManager wakeLockManager, AndroidWakeLockManager wakeLockManager,
@@ -75,7 +75,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return MAX_LATENCY; return MAX_LATENCY;
} }

View File

@@ -0,0 +1,44 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
private final Application app;
AndroidRemovableDrivePlugin(Application app, PluginCallback callback,
long maxLatency) {
super(callback, maxLatency);
this.app = app;
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openInputStream(Uri.parse(uri));
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openOutputStream(Uri.parse(uri));
}
}

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class AndroidRemovableDrivePluginFactory implements
SimplexPluginFactory {
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
private final Application app;
@Inject
AndroidRemovableDrivePluginFactory(Application app) {
this.app = app;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public long getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new AndroidRemovableDrivePlugin(app, callback, MAX_LATENCY);
}
}

View File

@@ -67,7 +67,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
Application app, Application app,
Backoff backoff, Backoff backoff,
PluginCallback callback, PluginCallback callback,
int maxLatency, long maxLatency,
int maxIdleTime, int maxIdleTime,
int connectionTimeout) { int connectionTimeout) {
super(ioExecutor, wakefulIoExecutor, backoff, callback, maxLatency, super(ioExecutor, wakefulIoExecutor, backoff, callback, maxLatency,

View File

@@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private final Application app; private final Application app;
@Inject @Inject
public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor, AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
EventBus eventBus, EventBus eventBus,
BackoffFactory backoffFactory, BackoffFactory backoffFactory,
@@ -55,7 +55,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return MAX_LATENCY; return MAX_LATENCY;
} }

View File

@@ -68,7 +68,7 @@ class AndroidTorPlugin extends TorPlugin {
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, PluginCallback callback,
String architecture, String architecture,
int maxLatency, long maxLatency,
int maxIdleTime, int maxIdleTime,
File torDirectory) { File torDirectory) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils, super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,

View File

@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
private final File torDirectory; private final File torDirectory;
@Inject @Inject
public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor, AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
Application app, Application app,
NetworkManager networkManager, NetworkManager networkManager,
@@ -94,7 +94,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return MAX_LATENCY; return MAX_LATENCY;
} }

View File

@@ -10,17 +10,20 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Scanner;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.lang.Runtime.getRuntime;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@@ -118,4 +121,17 @@ public class AndroidUtils {
public static String[] getSupportedImageContentTypes() { public static String[] getSupportedImageContentTypes() {
return new String[] {"image/jpeg", "image/png", "image/gif"}; return new String[] {"image/jpeg", "image/png", "image/gif"};
} }
@Nullable
public static String getSystemProperty(String propName) {
try {
Process p = getRuntime().exec("getprop " + propName);
Scanner s = new Scanner(p.getInputStream());
String line = s.nextLine();
s.close();
return line;
} catch (SecurityException | IOException e) {
return null;
}
}
} }

View File

@@ -1,42 +1,43 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', 'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.android.tools.analytics-library:protos:27.1.1:protos-27.1.1.jar:13f77e73762e58ab372d140b3a6be6903aea9775b62dd14fbc62d4cc7069c9a4', 'com.android.tools.analytics-library:protos:27.1.3:protos-27.1.3.jar:0d9e6cff60b318baac250b6f5bb076a8161103338bf2749cdf1db8a5a13a1f12',
'com.android.tools.analytics-library:shared:27.1.1:shared-27.1.1.jar:82930a52001410e97d809930b670f4de3002286975f046b9de5f6b777b06d366', 'com.android.tools.analytics-library:shared:27.1.3:shared-27.1.3.jar:10d2a51d8f89ff4ac849888e5a9c60b10e879c30d78545ec1da4d3df7bd56ae4',
'com.android.tools.analytics-library:tracker:27.1.1:tracker-27.1.1.jar:31bc5a00be0055bac89c9b2f34751883e987cd89e3ac1783720645c164f591d9', 'com.android.tools.analytics-library:tracker:27.1.3:tracker-27.1.3.jar:589b355a2ba796cbc0a2b2295737de6661f078262e5f87cd6f540b8d011e5ebb',
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca', 'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
'com.android.tools.build:apksig:4.1.1:apksig-4.1.1.jar:e0a69da9e5a03986d608b45bbf954ef0e6a0b3f58c1b8315bd169ec08b279e72', 'com.android.tools.build:apksig:4.1.3:apksig-4.1.3.jar:a851980c678ff7a6785388b9a9e8cc094788ce3c4a985ad2b19c2028fd3c631a',
'com.android.tools.build:apkzlib:4.1.1:apkzlib-4.1.1.jar:ba4b5e419b6be0130eae7f8301c3a551ad3976f487d2e0c6852ebb175ac41127', 'com.android.tools.build:apkzlib:4.1.3:apkzlib-4.1.3.jar:475903065e7e83a8c1ba78d267c97a54dc5a04d768b535093850423d7b11f2c8',
'com.android.tools.build:builder-model:4.1.1:builder-model-4.1.1.jar:e95c99cc298ad67b8deb6ced99c51abc8f59afebedad044b1a10dde14646a4dd', 'com.android.tools.build:builder-model:4.1.3:builder-model-4.1.3.jar:2624a1436c3ab39dd91d3ecf9409a594b0f89ea5cab255f2e9ff11f5ee03d274',
'com.android.tools.build:builder-test-api:4.1.1:builder-test-api-4.1.1.jar:464f596ab261c051c3847406748e843770dea123f6fa5fee8a9390644e709b7a', 'com.android.tools.build:builder-test-api:4.1.3:builder-test-api-4.1.3.jar:3d2af66726b06b53b8d6d497efcee39ff9f77eb2f8d2cce38b31502383a40d2c',
'com.android.tools.build:builder:4.1.1:builder-4.1.1.jar:0f78d4759d2f7b57b95865522ec34596ba419b9982f3b25e3449213f9c98b80d', 'com.android.tools.build:builder:4.1.3:builder-4.1.3.jar:a40426cd6d68f6a722ef4950058c075e4547025e8c2fd78e732ad89f15176f84',
'com.android.tools.build:gradle-api:4.1.1:gradle-api-4.1.1.jar:d42e6b539e4c1353ad3546e75ec8ce11a017b97481023e8ea18577eefe374358', 'com.android.tools.build:gradle-api:4.1.3:gradle-api-4.1.3.jar:11b1fb9de658bdcf9290b1c1517060d0c4d93f2b27975934989ca4ac890bc077',
'com.android.tools.build:manifest-merger:27.1.1:manifest-merger-27.1.1.jar:7a45fa143687859bb2e5a961dcf6ee88094d3853de0cb543dc03dbcb0f4b554b', 'com.android.tools.build:manifest-merger:27.1.3:manifest-merger-27.1.3.jar:ce8d4009b1f1584777a7ffa1da3b0551dc316bc8e08112e442c352af70f46f2d',
'com.android.tools.ddms:ddmlib:27.1.1:ddmlib-27.1.1.jar:da6e4bd834b6a85dae8019039849d8bd96933347dfbf460df74913ddade6e40a', 'com.android.tools.ddms:ddmlib:27.1.3:ddmlib-27.1.3.jar:8f76e8236d2b9eebf26378746dad025c4c7c056a02e133dae4ddef47b283c710',
'com.android.tools.external.com-intellij:intellij-core:27.1.1:intellij-core-27.1.1.jar:2591a7363c4443c59bf9f793730acafce9d6ec3076e2f46716edaf53a41b6fb6', 'com.android.tools.external.com-intellij:intellij-core:27.1.3:intellij-core-27.1.3.jar:652814fa099b4746fb6f10e19718e476952e8b5bac24e17d914f90650ad21808',
'com.android.tools.external.com-intellij:kotlin-compiler:27.1.1:kotlin-compiler-27.1.1.jar:5054ae770ba788f110303c65abd6b1fa28eccf52dee1274510e201b2b81885c8', 'com.android.tools.external.com-intellij:kotlin-compiler:27.1.3:kotlin-compiler-27.1.3.jar:8d7a78d5efd213c5e467e42bd205582aad73ffc77ee5dc18eb1361c9af72f125',
'com.android.tools.external.org-jetbrains:uast:27.1.1:uast-27.1.1.jar:54cd8f6886a9d2f5641659dd5c91f626629672cd48301f7f0bd6aad9bd448714', 'com.android.tools.external.org-jetbrains:uast:27.1.3:uast-27.1.3.jar:aea53944a1ac6a05f12297b55290e8cbecfe54c4166260cfba4405823bfe1c78',
'com.android.tools.layoutlib:layoutlib-api:27.1.1:layoutlib-api-27.1.1.jar:8a9a22e3b309521ea83b724e5a89cfdac6076f52d675c0e17d77b05527bc0f8c', 'com.android.tools.layoutlib:layoutlib-api:27.1.3:layoutlib-api-27.1.3.jar:23875ce0a8429f33a4e86cc358f658faa0ba9c576f5f05760e544b453d67d04b',
'com.android.tools.lint:lint-api:27.1.1:lint-api-27.1.1.jar:c1d8176094cb0478786070d40533efb578ebc53529a82f6ef5bee879bdca418b', 'com.android.tools.lint:lint-api:27.1.3:lint-api-27.1.3.jar:97666be32bcadacd944416ea334a9575ef8f4ad0c8f333151491ff4a7df43e1c',
'com.android.tools.lint:lint-checks:27.1.1:lint-checks-27.1.1.jar:3899c91e00bd059b40c31a9ca00cd0f8303191947608735ae1b657323693fb61', 'com.android.tools.lint:lint-checks:27.1.3:lint-checks-27.1.3.jar:b2d71ae84a31490fe9ff26c706163fe245b2aea98e3eb747214c1085df444708',
'com.android.tools.lint:lint-gradle-api:27.1.1:lint-gradle-api-27.1.1.jar:26aa89d38b9825cc73229daa82a68875801c8b8491f30497ce62aff1f206eb0d', 'com.android.tools.lint:lint-gradle-api:27.1.3:lint-gradle-api-27.1.3.jar:e54131c287a2954e6ed78a3351e5e10e35a1da2f09ac443bf44b705c71b63a4d',
'com.android.tools.lint:lint-gradle:27.1.1:lint-gradle-27.1.1.jar:f7355823ead869f4d28184ba28b7a0c693b507519a2d3705bb9848a0f35b3756', 'com.android.tools.lint:lint-gradle:27.1.3:lint-gradle-27.1.3.jar:6a79e48943649d63665db7b17dbaff7af93e94ab9b15072f1a4d90486294ee9f',
'com.android.tools.lint:lint-model:27.1.1:lint-model-27.1.1.jar:bc23c0c413bdfca59dac2cd56b870d8360d009e9ec0d365e71f774bcf127971d', 'com.android.tools.lint:lint-model:27.1.3:lint-model-27.1.3.jar:acb9e792db7000e38e3c3ca21a9b14f2de6549d7a3fc92a97ffba3d06345e5bf',
'com.android.tools.lint:lint:27.1.1:lint-27.1.1.jar:2f6038a5398a42bd591883c3f5e5894f4ec52ca1c3683bf94fa8553c1700af81', 'com.android.tools.lint:lint:27.1.3:lint-27.1.3.jar:5a2e69d0901a3a476a5b2d5001de755868113145f5f6aa557750cfad5389a44b',
'com.android.tools:annotations:27.1.1:annotations-27.1.1.jar:ff28c504d2acb9fd1a5ffbd97ae85cf59ee18c76927525aad250509bccf2cab1', 'com.android.tools:annotations:27.1.3:annotations-27.1.3.jar:904dd771883496d5dfc86619ab2555968ea4e8a29d7a5f4f7cae6fbf5429f8f5',
'com.android.tools:common:27.1.1:common-27.1.1.jar:63d9a2a9ad6d278db319f3749b9f50bdf5457ef7020074a1bebe124e714b535c', 'com.android.tools:common:27.1.3:common-27.1.3.jar:17ab4728e3ea50f047dd5937f0faf35f2c5416962ed74891057087ddc328bf96',
'com.android.tools:dvlib:27.1.1:dvlib-27.1.1.jar:998a54201fc1cefee5f2399215e95c42b1f64f9e1d8f4452eb8255c68ba5440f', 'com.android.tools:dvlib:27.1.3:dvlib-27.1.3.jar:cead1c0c356cbe43e6855b0330fe09ef4bec2c72e22bdb4c6e7cf7e6b1dfbc37',
'com.android.tools:repository:27.1.1:repository-27.1.1.jar:d25b74ccabf4d876903efb375e9af6fb380d8ae0445bb74bbdcc225c1e37fa1d', 'com.android.tools:repository:27.1.3:repository-27.1.3.jar:99de1a178855b56b8cd91a56296f1e0a9399c445e6acc51f1d2927947cc472cb',
'com.android.tools:sdk-common:27.1.1:sdk-common-27.1.1.jar:4473ae97d0ef7061ee1de61041d5aa97405ae08e44c09cf7bb278b42e4b97c7c', 'com.android.tools:sdk-common:27.1.3:sdk-common-27.1.3.jar:b591e2aa0f1be600795f5c9e2bf81cba9b052bee452fc86c3362b5dd9e427a14',
'com.android.tools:sdklib:27.1.1:sdklib-27.1.1.jar:08e6b83961ac9724b3c1e3d0eff971f13be6701292c77914b8794480f3391250', 'com.android.tools:sdklib:27.1.3:sdklib-27.1.3.jar:ad6c08a45fe2904d05656bdddf9f623fa5c1d16bbd7b8d6a270a0734136ae02e',
'com.android:signflinger:4.1.1:signflinger-4.1.1.jar:0c66825988873ec2d51057fa463f54a8f18fc7326ff4530b9da363b71e97ce60', 'com.android:signflinger:4.1.3:signflinger-4.1.3.jar:f3103b55ccdc8dd9ee2517eb26af93b904d41303726594372d0df59d51156e5c',
'com.android:zipflinger:4.1.1:zipflinger-4.1.1.jar:0a8c3e52ac13dd031236f9fb5ba4408b1d5dcd12325a05440b36da09d8881446', 'com.android:zipflinger:4.1.3:zipflinger-4.1.3.jar:48569896c0497268308a8014c66eb0f2bace2b9e2fc9390f3012823fb86387d5',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81', 'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.dagger:dagger-compiler:2.24:dagger-compiler-2.24.jar:3c5afb955fb188da485cb2c048eff37dce0e1530b9780a0f2f7187d16d1ccc1f', 'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
'com.google.dagger:dagger-producers:2.24:dagger-producers-2.24.jar:f10f45b95191954d5d6b043fca9e62fb621d21bf70634b8f8476c7988b504c3a', 'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8',
'com.google.dagger:dagger-spi:2.24:dagger-spi-2.24.jar:c038445d14dbcb4054e61bf49e05009edf26fce4fdc7ec1a9db544784f68e718', 'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4',
'com.google.dagger:dagger:2.24:dagger-2.24.jar:550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64', 'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a', 'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d', 'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30', 'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
@@ -50,7 +51,7 @@ dependencyVerification {
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd', 'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.10.0:protobuf-java-3.10.0.jar:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9', 'com.google.protobuf:protobuf-java:3.10.0:protobuf-java-3.10.0.jar:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9',
'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439', 'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
'com.squareup:javapoet:1.11.1:javapoet-1.11.1.jar:9cbf2107be499ec6e95afd36b58e3ca122a24166cdd375732e51267d64058e90', 'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0', 'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce', 'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:3.0.7:istack-commons-runtime-3.0.7.jar:6443e10ba2e259fb821d9b6becf10db5316285fc30c53cec9d7b19a3877e7fdf', 'com.sun.istack:istack-commons-runtime:3.0.7:istack-commons-runtime-3.0.7.jar:6443e10ba2e259fb821d9b6becf10db5316285fc30c53cec9d7b19a3877e7fdf',
@@ -62,21 +63,21 @@ dependencyVerification {
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06', 'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', 'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5', 'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2', 'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6', 'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7', 'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd', 'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e', 'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', '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.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82', 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
'org.briarproject:tor-android:0.3.5.13:tor-android-0.3.5.13.zip:e0978db136731dae07774b722970cdae1e462fb5adc82845dd80a7e2d87f356c', 'org.briarproject:tor-android:0.3.5.15:tor-android-0.3.5.15.jar:560c5070166300b396cb2f28d82d9f639ee1fb5479096a3cef67da56d39937ad',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', '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.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df', 'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
@@ -85,26 +86,31 @@ dependencyVerification {
'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d', 'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f', 'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f',
'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60', 'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40', 'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4', 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72:kotlin-stdlib-jdk8-1.3.72.jar:133da70cfc07b56094282eac5c59bccd59f167ee2ead22e5282876d8bc10bf95', 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72:kotlin-stdlib-jdk8-1.3.72.jar:133da70cfc07b56094282eac5c59bccd59f167ee2ead22e5282876d8bc10bf95',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b', 'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7', 'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478', 'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c', 'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b', 'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760', 'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77', 'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80', 'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474', 'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d', 'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c', 'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145', 'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf', 'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
] ]
} }

View File

@@ -7,13 +7,13 @@ apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
dependencies { dependencies {
implementation "com.google.dagger:dagger:2.24" implementation "com.google.dagger:dagger:$dagger_version"
implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
testImplementation 'junit:junit:4.12' testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:2.8.2" testImplementation "org.jmock:jmock-legacy:$jmock_version"
signature 'org.codehaus.mojo.signature:java16:1.1@signature' signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface Consumer<T> {
void accept(T t);
}

View File

@@ -12,4 +12,8 @@ public interface FeatureFlags {
boolean shouldEnableDisappearingMessages(); boolean shouldEnableDisappearingMessages();
boolean shouldEnableConnectViaBluetooth(); boolean shouldEnableConnectViaBluetooth();
boolean shouldEnableShareAppViaOfflineHotspot();
boolean shouldEnableTransferData();
} }

View File

@@ -32,28 +32,31 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* <p>
* If an unexpected exception occurs while handling data that is assumed
* to be valid (e.g. locally created metadata), it may be sensible to
* rethrow the unexpected exception as a DbException so that delivery is
* attempted again at next startup. This will allow delivery to succeed if
* the unexpected exception was caused by a bug that has subsequently been
* fixed.
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @return Whether or not this message should be shared * @throws DbException if a database error occurs while delivering the
* @throws DbException Should only be used for real database errors. * message. Delivery will be attempted again at next startup. Throwing
* If this is thrown, delivery will be attempted again at next startup, * this exception has the same effect as returning
* whereas if a FormatException is thrown, the message will be permanently * {@link DeliveryAction#DEFER}.
* invalidated. * @throws FormatException if the message is invalid in the context of its
* @throws FormatException Use this for any non-database error * dependencies. The message and any dependents will be marked as invalid
* that occurs while handling remotely created data. * and deleted along with their metadata. Throwing this exception has the
* This includes errors that occur while handling locally created data * same effect as returning {@link DeliveryAction#REJECT}.
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Never rethrow DbException as FormatException!
*/ */
protected abstract boolean incomingMessage(Transaction txn, Message m, protected abstract DeliveryAction incomingMessage(Transaction txn,
BdfList body, BdfDictionary meta) throws DbException, Message m, BdfList body, BdfDictionary meta)
FormatException; throws DbException, FormatException;
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
try { try {
BdfList body = clientHelper.toList(m); BdfList body = clientHelper.toList(m);
BdfDictionary metaDictionary = metadataParser.parse(meta); BdfDictionary metaDictionary = metadataParser.parse(meta);

View File

@@ -1,23 +1,19 @@
package org.briarproject.bramble.api.crypto; package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.Bytes;
/** /**
* A secret key used for encryption and/or authentication. * A secret key used for encryption and/or authentication.
*/ */
public class SecretKey { public class SecretKey extends Bytes {
/** /**
* The length of a secret key in bytes. * The length of a secret key in bytes.
*/ */
public static final int LENGTH = 32; public static final int LENGTH = 32;
private final byte[] key;
public SecretKey(byte[] key) { public SecretKey(byte[] key) {
super(key);
if (key.length != LENGTH) throw new IllegalArgumentException(); if (key.length != LENGTH) throw new IllegalArgumentException();
this.key = key;
}
public byte[] getBytes() {
return key;
} }
} }

View File

@@ -101,7 +101,7 @@ public interface DatabaseComponent extends TransactionManager {
/** /**
* Stores a transport. * Stores a transport.
*/ */
void addTransport(Transaction txn, TransportId t, int maxLatency) void addTransport(Transaction txn, TransportId t, long maxLatency)
throws DbException; throws DbException;
/** /**
@@ -118,6 +118,18 @@ public interface DatabaseComponent extends TransactionManager {
KeySetId addTransportKeys(Transaction txn, PendingContactId p, KeySetId addTransportKeys(Transaction txn, PendingContactId p,
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.
@@ -150,6 +162,16 @@ public interface DatabaseComponent extends TransactionManager {
boolean containsPendingContact(Transaction txn, PendingContactId p) boolean containsPendingContact(Transaction txn, PendingContactId p)
throws DbException; throws DbException;
/**
* Returns true if the database contains keys for communicating with the
* given contact over the given transport. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
boolean containsTransportKeys(Transaction txn, ContactId c, TransportId t)
throws DbException;
/** /**
* Deletes the message with the given ID. Unlike * Deletes the message with the given ID. Unlike
* {@link #removeMessage(Transaction, MessageId)}, the message ID, * {@link #removeMessage(Transaction, MessageId)}, the message ID,
@@ -178,7 +200,19 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
@Nullable @Nullable
Collection<Message> generateBatch(Transaction txn, ContactId c, Collection<Message> generateBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException; int maxLength, long maxLatency) throws DbException;
/**
* Returns a batch of messages for the given contact containing the
* messages with the given IDs, for transmission over a transport with
* the given maximum latency.
* <p/>
* If any of the given messages are not in the database or are not visible
* to the contact, they are omitted from the batch without throwing an
* exception.
*/
Collection<Message> generateBatch(Transaction txn, ContactId c,
Collection<MessageId> ids, long maxLatency) throws DbException;
/** /**
* Returns an offer for the given contact for transmission over a * Returns an offer for the given contact for transmission over a
@@ -187,7 +221,7 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
@Nullable @Nullable
Offer generateOffer(Transaction txn, ContactId c, int maxMessages, Offer generateOffer(Transaction txn, ContactId c, int maxMessages,
int maxLatency) throws DbException; long maxLatency) throws DbException;
/** /**
* Returns a request for the given contact, or null if there are no * Returns a request for the given contact, or null if there are no
@@ -206,7 +240,7 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
@Nullable @Nullable
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c, Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException; int maxLength, long maxLatency) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.
@@ -426,6 +460,27 @@ public interface DatabaseComponent extends TransactionManager {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact, together with their raw lengths. This may include
* messages that have already been sent and are not yet due for
* retransmission.
* <p/>
* Read-only.
*/
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
ContactId c) throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages
* that have already been sent and are not yet due for retransmission.
* <p/>
* Read-only.
*/
long getUnackedMessageBytesToSend(Transaction txn, ContactId c)
throws DbException;
/** /**
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE} * message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE}
@@ -483,6 +538,16 @@ public interface DatabaseComponent extends TransactionManager {
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t) Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
throws DbException; throws DbException;
/**
* Returns the contact IDs and transport IDs for which the DB contains
* at least one set of transport keys. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Transaction txn) throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */

View File

@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.keyagreement.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
/** /**
* An event that is broadcast when a BQP protocol completes. * An event that is broadcast when a BQP protocol begins.
*/ */
public class KeyAgreementStartedEvent extends Event { public class KeyAgreementStartedEvent extends Event {
} }

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Wakeful; import org.briarproject.bramble.api.system.Wakeful;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -22,6 +23,7 @@ public interface LifecycleManager {
*/ */
enum StartResult { enum StartResult {
ALREADY_RUNNING, ALREADY_RUNNING,
CLOCK_ERROR,
DB_ERROR, DB_ERROR,
DATA_TOO_OLD_ERROR, DATA_TOO_OLD_ERROR,
DATA_TOO_NEW_ERROR, DATA_TOO_NEW_ERROR,
@@ -65,6 +67,10 @@ public interface LifecycleManager {
/** /**
* Opens the {@link DatabaseComponent} using the given key and starts any * Opens the {@link DatabaseComponent} using the given key and starts any
* registered {@link Service Services}. * registered {@link Service Services}.
*
* @return {@link StartResult#CLOCK_ERROR} if the system clock is earlier
* than {@link Clock#MIN_REASONABLE_TIME_MS} or later than
* {@link Clock#MAX_REASONABLE_TIME_MS}.
*/ */
@Wakeful @Wakeful
StartResult startServices(SecretKey dbKey); StartResult startServices(SecretKey dbKey);

View File

@@ -61,7 +61,7 @@ public interface Plugin {
/** /**
* Returns the transport's maximum latency in milliseconds. * Returns the transport's maximum latency in milliseconds.
*/ */
int getMaxLatency(); long getMaxLatency();
/** /**
* Returns the transport's maximum idle time in milliseconds. * Returns the transport's maximum idle time in milliseconds.

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
public interface PluginFactory<P extends Plugin> {
/**
* Returns the plugin's transport identifier.
*/
TransportId getId();
/**
* Returns the maximum latency of the transport in milliseconds.
*/
long getMaxLatency();
/**
* Creates and returns a plugin, or null if no plugin can be created.
*/
@Nullable
P createPlugin(PluginCallback callback);
}

View File

@@ -15,13 +15,18 @@ public interface TransportConnectionWriter {
/** /**
* Returns the maximum latency of the transport in milliseconds. * Returns the maximum latency of the transport in milliseconds.
*/ */
int getMaxLatency(); long getMaxLatency();
/** /**
* Returns the maximum idle time of the transport in milliseconds. * Returns the maximum idle time of the transport in milliseconds.
*/ */
int getMaxIdleTime(); int getMaxIdleTime();
/**
* Returns true if the transport is lossy and cheap.
*/
boolean isLossyAndCheap();
/** /**
* Returns an output stream for writing to the transport connection. * Returns an output stream for writing to the transport connection.
*/ */

View File

@@ -70,7 +70,7 @@ public abstract class AbstractDuplexTransportConnection
private class Writer implements TransportConnectionWriter { private class Writer implements TransportConnectionWriter {
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }
@@ -79,6 +79,11 @@ public abstract class AbstractDuplexTransportConnection
return plugin.getMaxIdleTime(); return plugin.getMaxIdleTime();
} }
@Override
public boolean isLossyAndCheap() {
return false;
}
@Override @Override
public OutputStream getOutputStream() throws IOException { public OutputStream getOutputStream() throws IOException {
return AbstractDuplexTransportConnection.this.getOutputStream(); return AbstractDuplexTransportConnection.this.getOutputStream();

View File

@@ -1,30 +1,11 @@
package org.briarproject.bramble.api.plugin.duplex; package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginFactory;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.Nullable;
/** /**
* Factory for creating a plugin for a duplex transport. * Factory for creating a plugin for a duplex transport.
*/ */
@NotNullByDefault @NotNullByDefault
public interface DuplexPluginFactory { public interface DuplexPluginFactory extends PluginFactory<DuplexPlugin> {
/**
* Returns the plugin's transport identifier.
*/
TransportId getId();
/**
* Returns the maximum latency of the transport in milliseconds.
*/
int getMaxLatency();
/**
* Creates and returns a plugin, or null if no plugin can be created.
*/
@Nullable
DuplexPlugin createPlugin(PluginCallback callback);
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin.file;
public interface FileConstants { public interface FileConstants {

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.plugin.TransportId;
public interface RemovableDriveConstants {
TransportId ID = new TransportId("org.briarproject.bramble.drive");
String PROP_PATH = "path";
String PROP_URI = "uri";
String PROP_SUPPORTED = "supported";
}

View File

@@ -0,0 +1,52 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import javax.annotation.Nullable;
@NotNullByDefault
public interface RemovableDriveManager {
/**
* Returns the currently running reader task, or null if no reader task
* is running.
*/
@Nullable
RemovableDriveTask getCurrentReaderTask();
/**
* Returns the currently running writer task, or null if no writer task
* is running.
*/
@Nullable
RemovableDriveTask getCurrentWriterTask();
/**
* Starts and returns a reader task, reading from a stream described by
* the given transport properties. If a reader task is already running,
* it will be returned and the argument will be ignored.
*/
RemovableDriveTask startReaderTask(TransportProperties p);
/**
* Starts and returns a writer task for the given contact, writing to
* a stream described by the given transport properties. If a writer task
* is already running, it will be returned and the arguments will be
* ignored.
*/
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
/**
* Returns true if the given contact has indicated support for the
* removable drive transport.
*/
boolean isTransportSupportedByContact(ContactId c) throws DbException;
/**
* Returns true if there is anything to send to the given contact.
*/
boolean isWriterTaskNeeded(ContactId c) throws DbException;
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
public interface RemovableDriveTask extends Runnable {
/**
* Returns the {@link TransportProperties} that were used for creating
* this task.
*/
TransportProperties getTransportProperties();
/**
* Adds an observer to the task. The observer will be notified of state
* changes on the event thread. If the task has already finished, the
* observer will be notified of its final state.
*/
void addObserver(Consumer<State> observer);
/**
* Removes an observer from the task.
*/
void removeObserver(Consumer<State> observer);
class State {
private final long done, total;
private final boolean finished, success;
public State(long done, long total, boolean finished, boolean success) {
this.done = done;
this.total = total;
this.finished = finished;
this.success = success;
}
/**
* Returns the total length in bytes of the messages read or written
* so far, or zero if the total is unknown.
*/
public long getDone() {
return done;
}
/**
* Returns the total length in bytes of the messages that will have
* been read or written when the task is complete, or zero if the
* total is unknown.
*/
public long getTotal() {
return total;
}
public boolean isFinished() {
return finished;
}
public boolean isSuccess() {
return success;
}
}
}

View File

@@ -15,6 +15,12 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface SimplexPlugin extends Plugin { public interface SimplexPlugin extends Plugin {
/**
* Returns true if the transport is likely to lose streams and the cost of
* transmitting redundant copies of data is cheap.
*/
boolean isLossyAndCheap();
/** /**
* Attempts to create and return a reader for the given transport * Attempts to create and return a reader for the given transport
* properties. Returns null if a reader cannot be created. * properties. Returns null if a reader cannot be created.

View File

@@ -1,30 +1,11 @@
package org.briarproject.bramble.api.plugin.simplex; package org.briarproject.bramble.api.plugin.simplex;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginFactory;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.Nullable;
/** /**
* Factory for creating a plugin for a simplex transport. * Factory for creating a plugin for a simplex transport.
*/ */
@NotNullByDefault @NotNullByDefault
public interface SimplexPluginFactory { public interface SimplexPluginFactory extends PluginFactory<SimplexPlugin> {
/**
* Returns the plugin's transport identifier.
*/
TransportId getId();
/**
* Returns the maximum latency of the transport in milliseconds.
*/
int getMaxLatency();
/**
* Creates and returns a plugin, or null if no plugin can be created.
*/
@Nullable
SimplexPlugin createPlugin(PluginCallback callback);
} }

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.UniqueId;
import java.util.List; import java.util.List;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES; import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
public interface SyncConstants { public interface SyncConstants {
@@ -55,4 +56,9 @@ public interface SyncConstants {
* connections. * connections.
*/ */
int PRIORITY_NONCE_BYTES = 16; int PRIORITY_NONCE_BYTES = 16;
/**
* The maximum allowed latency for any transport, in milliseconds.
*/
long MAX_TRANSPORT_LATENCY = DAYS.toMillis(365);
} }

View File

@@ -16,9 +16,9 @@ public interface SyncSessionFactory {
PriorityHandler handler); PriorityHandler handler);
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter); long maxLatency, boolean eager, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, StreamWriter streamWriter, long maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority); @Nullable Priority priority);
} }

View File

@@ -18,11 +18,13 @@ public class MessagesSentEvent extends Event {
private final ContactId contactId; private final ContactId contactId;
private final Collection<MessageId> messageIds; private final Collection<MessageId> messageIds;
private final long totalLength;
public MessagesSentEvent(ContactId contactId, public MessagesSentEvent(ContactId contactId,
Collection<MessageId> messageIds) { Collection<MessageId> messageIds, long totalLength) {
this.contactId = contactId; this.contactId = contactId;
this.messageIds = messageIds; this.messageIds = messageIds;
this.totalLength = totalLength;
} }
public ContactId getContactId() { public ContactId getContactId() {
@@ -32,4 +34,8 @@ public class MessagesSentEvent extends Event {
public Collection<MessageId> getMessageIds() { public Collection<MessageId> getMessageIds() {
return messageIds; return messageIds;
} }
public long getTotalLength() {
return totalLength;
}
} }

View File

@@ -10,23 +10,54 @@ public interface IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* <p>
* If an unexpected exception occurs while handling data that is assumed
* to be valid (e.g. locally created metadata), it may be sensible to
* rethrow the unexpected exception as a DbException so that delivery is
* attempted again at next startup. This will allow delivery to succeed if
* the unexpected exception was caused by a bug that has subsequently been
* fixed.
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @return Whether or not this message should be shared * @throws DbException if a database error occurs while delivering the
* @throws DbException Should only be used for real database errors. * message. Delivery will be attempted again at next startup. Throwing
* If this is thrown, delivery will be attempted again at next startup, * this exception has the same effect as returning
* whereas if an InvalidMessageException is thrown, * {@link DeliveryAction#DEFER}.
* the message will be permanently invalidated. * @throws InvalidMessageException if the message is invalid in the context
* @throws InvalidMessageException for any non-database error * of its dependencies. The message and any dependents will be marked as
* that occurs while handling remotely created data. * invalid and deleted along with their metadata. Throwing this exception
* This includes errors that occur while handling locally created data * has the same effect as returning {@link DeliveryAction#REJECT}.
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Throwing this will delete the incoming message and its metadata
* marking it as invalid in the database.
* Never rethrow DbException as InvalidMessageException!
*/ */
boolean incomingMessage(Transaction txn, Message m, Metadata meta) DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta)
throws DbException, InvalidMessageException; throws DbException, InvalidMessageException;
enum DeliveryAction {
/**
* The message and any dependent messages will be moved to the
* {@link MessageState#INVALID INVALID state} and deleted, along with
* their metadata.
*/
REJECT,
/**
* The message will be moved to the
* {@link MessageState#PENDING PENDING state}. Delivery will be
* attempted again at next startup.
*/
DEFER,
/**
* The message will be moved to the
* {@link MessageState#DELIVERED DELIVERED state} and shared.
*/
ACCEPT_SHARE,
/**
* The message will be moved to the
* {@link MessageState#DELIVERED DELIVERED state} and will not be
* shared.
*/
ACCEPT_DO_NOT_SHARE
}
} }

View File

@@ -1,8 +1,33 @@
package org.briarproject.bramble.api.sync.validation; package org.briarproject.bramble.api.sync.validation;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
public enum MessageState { public enum MessageState {
UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3); /**
* A remote message that has not yet been validated.
*/
UNKNOWN(0),
/**
* A remote message that has failed validation, has been
* {@link DeliveryAction#REJECT rejected} by the local sync client, or
* depends on another message that has failed validation or been rejected.
*/
INVALID(1),
/**
* A remote message that has passed validation and is awaiting delivery to
* the local sync client. The message will not be delivered until all its
* dependencies have been validated and delivered.
*/
PENDING(2),
/**
* A local message, or a remote message that has passed validation and
* been delivered to the local sync client.
*/
DELIVERED(3);
private final int value; private final int value;

View File

@@ -6,6 +6,22 @@ package org.briarproject.bramble.api.system;
*/ */
public interface Clock { public interface Clock {
/**
* The minimum reasonable value for the system clock, in milliseconds
* since the Unix epoch.
* <p/>
* 1 Jan 2021, 00:00:00 UTC
*/
long MIN_REASONABLE_TIME_MS = 1_609_459_200_000L;
/**
* The maximum reasonable value for the system clock, in milliseconds
* since the Unix epoch.
* <p/>
* 1 Jan 2121, 00:00:00 UTC
*/
long MAX_REASONABLE_TIME_MS = 4_765_132_800_000L;
/** /**
* @see System#currentTimeMillis() * @see System#currentTimeMillis()
*/ */

View File

@@ -22,8 +22,24 @@ public interface KeyManager {
/** /**
* Derives and stores a set of rotation mode transport keys for * Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over each transport and returns the * communicating with the given contact over the given transport and
* key set IDs. * returns the key set ID, or null if the transport is not supported.
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
*
* @param alice True if the local party is Alice
* @param active Whether the derived keys can be used for outgoing streams
*/
@Nullable
KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t,
SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
/**
* Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over each supported transport and
* returns the key set IDs.
* <p/> * <p/>
* {@link StreamContext StreamContexts} for the contact can be created * {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned. * after this method has returned.

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.api.transport.agreement;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
@NotNullByDefault
public interface TransportKeyAgreementManager {
/**
* The unique ID of the transport key agreement client.
*/
ClientId CLIENT_ID =
new ClientId("org.briarproject.bramble.transport.agreement");
/**
* The current major version of the transport key agreement client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the transport key agreement client.
*/
int MINOR_VERSION = 0;
}

View File

@@ -163,10 +163,15 @@ public class TestUtils {
public static Message getMessage(GroupId groupId) { public static Message getMessage(GroupId groupId) {
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH); int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
return getMessage(groupId, bodyLength); return getMessage(groupId, bodyLength, timestamp);
} }
public static Message getMessage(GroupId groupId, int bodyLength) { public static Message getMessage(GroupId groupId, int bodyLength) {
return getMessage(groupId, bodyLength, timestamp);
}
public static Message getMessage(GroupId groupId, int bodyLength,
long timestamp) {
MessageId id = new MessageId(getRandomId()); MessageId id = new MessageId(getRandomId());
byte[] body = getRandomBytes(bodyLength); byte[] body = getRandomBytes(bodyLength);
return new Message(id, groupId, timestamp, body); return new Message(id, groupId, timestamp, body);

View File

@@ -1,24 +1,27 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', 'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger:2.24:dagger-2.24.jar:550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64', 'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90', 'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52', 'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c', 'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b', 'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760', 'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80', 'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d', 'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220', 'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
] ]
} }

View File

@@ -17,16 +17,17 @@ dependencies {
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.3' implementation 'org.briarproject:jtorctl:0.3'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6 testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'junit:junit:4.12' testImplementation 'net.jodah:concurrentunit:0.4.2'
testImplementation "org.jmock:jmock:2.8.2" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-legacy:2.8.2" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.24' testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
signature 'org.codehaus.mojo.signature:java16:1.1@signature' signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
public interface BrambleCoreEagerSingletons { public interface BrambleCoreEagerSingletons {
@@ -33,6 +34,8 @@ public interface BrambleCoreEagerSingletons {
void inject(RendezvousModule.EagerSingletons init); void inject(RendezvousModule.EagerSingletons init);
void inject(TransportKeyAgreementModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init); void inject(TransportModule.EagerSingletons init);
void inject(ValidationModule.EagerSingletons init); void inject(ValidationModule.EagerSingletons init);
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
c.inject(new RendezvousModule.EagerSingletons()); c.inject(new RendezvousModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons()); c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons());
c.inject(new TransportKeyAgreementModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons());
c.inject(new ValidationModule.EagerSingletons()); c.inject(new ValidationModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons()); c.inject(new VersioningModule.EagerSingletons());

View File

@@ -23,6 +23,7 @@ import org.briarproject.bramble.settings.SettingsModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
import dagger.Module; import dagger.Module;
@@ -49,6 +50,7 @@ import dagger.Module;
RendezvousModule.class, RendezvousModule.class,
SettingsModule.class, SettingsModule.class,
SyncModule.class, SyncModule.class,
TransportKeyAgreementModule.class,
TransportModule.class, TransportModule.class,
ValidationModule.class, ValidationModule.class,
VersioningModule.class VersioningModule.class

View File

@@ -45,7 +45,6 @@ abstract class Connection {
@Nullable @Nullable
StreamContext recogniseTag(TransportConnectionReader reader, StreamContext recogniseTag(TransportConnectionReader reader,
TransportId transportId) { TransportId transportId) {
StreamContext ctx;
try { try {
byte[] tag = readTag(reader.getInputStream()); byte[] tag = readTag(reader.getInputStream());
return keyManager.getStreamContext(transportId, tag); return keyManager.getStreamContext(transportId, tag);

View File

@@ -71,8 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId()); ContactId c = requireNonNull(ctx.getContactId());
// Use eager retransmission if the transport is lossy and cheap
return syncSessionFactory.createSimplexOutgoingSession(c, return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), streamWriter); ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
streamWriter);
} }
} }

View File

@@ -47,6 +47,7 @@ import javax.inject.Inject;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS;
import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.contact.ContactExchangeRecordTypes.CONTACT_INFO; import static org.briarproject.bramble.contact.ContactExchangeRecordTypes.CONTACT_INFO;
import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@@ -184,6 +185,10 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
// The agreed timestamp is the minimum of the peers' timestamps // The agreed timestamp is the minimum of the peers' timestamps
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
if (timestamp < MIN_REASONABLE_TIME_MS) {
LOG.warning("Timestamp is too old");
throw new FormatException();
}
// Add the contact // Add the contact
Contact contact = addContact(p, remoteInfo.author, localAuthor, Contact contact = addContact(p, remoteInfo.author, localAuthor,

View File

@@ -145,7 +145,7 @@ interface Database<T> {
/** /**
* Stores a transport. * Stores a transport.
*/ */
void addTransport(T txn, TransportId t, int maxLatency) void addTransport(T txn, TransportId t, long maxLatency)
throws DbException; throws DbException;
/** /**
@@ -162,6 +162,18 @@ interface Database<T> {
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k) KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
throws DbException; throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.
@@ -215,6 +227,16 @@ interface Database<T> {
*/ */
boolean containsTransport(T txn, TransportId t) throws DbException; boolean containsTransport(T txn, TransportId t) throws DbException;
/**
* Returns true if the database contains keys for communicating with the
* given contact over the given transport. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
boolean containsTransportKeys(T txn, ContactId c, TransportId t)
throws DbException;
/** /**
* Returns true if the database contains the given message, the message is * Returns true if the database contains the given message, the message is
* shared, and the visibility of the message's group to the given contact * shared, and the visibility of the message's group to the given contact
@@ -461,7 +483,7 @@ interface Database<T> {
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToOffer(T txn, ContactId c, Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
int maxMessages, int maxLatency) throws DbException; int maxMessages, long maxLatency) throws DbException;
/** /**
* Returns the IDs of some messages that are eligible to be requested from * Returns the IDs of some messages that are eligible to be requested from
@@ -476,10 +498,36 @@ interface Database<T> {
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
* given contact, up to the given total length. * given contact, up to the given total length.
* <p/> * <p/>
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
* does not return messages that have already been sent unless they are
* due for retransmission.
* <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength, Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
int maxLatency) throws DbException; long maxLatency) throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact, together with their raw lengths.
* <p/>
* Unlike {@link #getMessagesToSend(Object, ContactId, int, long)} this
* method may return messages that have already been sent and are not yet
* due for retransmission.
* <p/>
* Read-only.
*/
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages
* that have already been sent and are not yet due for retransmission.
* <p/>
* Read-only.
*/
long getUnackedMessageBytesToSend(T txn, ContactId c) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated.
@@ -556,7 +604,7 @@ interface Database<T> {
* Read-only. * Read-only.
*/ */
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c, Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
int maxLength, int maxLatency) throws DbException; int maxLength, long maxLatency) throws DbException;
/** /**
* Returns all settings in the given namespace. * Returns all settings in the given namespace.
@@ -580,6 +628,16 @@ interface Database<T> {
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t) Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
throws DbException; throws DbException;
/**
* Returns the contact IDs and transport IDs for which the DB contains
* at least one set of transport keys. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(T txn)
throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */
@@ -787,7 +845,7 @@ interface Database<T> {
* of the given message with respect to the given contact, using the latency * of the given message with respect to the given contact, using the latency
* of the transport over which it was sent. * of the transport over which it was sent.
*/ */
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, int maxLatency) void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, long maxLatency)
throws DbException; throws DbException;
/** /**

View File

@@ -310,7 +310,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void addTransport(Transaction transaction, TransportId t, public void addTransport(Transaction transaction, TransportId t,
int maxLatency) throws DbException { long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsTransport(txn, t)) if (!db.containsTransport(txn, t))
@@ -341,6 +341,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.addTransportKeys(txn, p, k); return db.addTransportKeys(txn, p, k);
} }
@Override
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
long maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsAnythingToSend(txn, c, maxLatency, eager);
}
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId remote, public boolean containsContact(Transaction transaction, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -371,6 +380,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.containsPendingContact(txn, p); return db.containsPendingContact(txn, p);
} }
@Override
public boolean containsTransportKeys(Transaction transaction, ContactId c,
TransportId t) throws DbException {
T txn = unbox(transaction);
return db.containsTransportKeys(txn, c, t);
}
@Override @Override
public void deleteMessage(Transaction transaction, MessageId m) public void deleteMessage(Transaction transaction, MessageId m)
throws DbException { throws DbException {
@@ -408,28 +424,57 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable @Nullable
@Override @Override
public Collection<Message> generateBatch(Transaction transaction, public Collection<Message> generateBatch(Transaction transaction,
ContactId c, int maxLength, int maxLatency) throws DbException { ContactId c, int maxLength, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, c, maxLength, maxLatency); db.getMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
messages.add(db.getMessage(txn, m)); Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null; if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids)); transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages;
}
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, Collection<MessageId> ids, long maxLatency)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
List<MessageId> sentIds = new ArrayList<>(ids.size());
for (MessageId m : ids) {
if (db.containsVisibleMessage(txn, c, m)) {
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
sentIds.add(m);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
}
if (messages.isEmpty()) return messages;
db.lowerRequestedFlag(txn, c, sentIds);
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
return messages; return messages;
} }
@Nullable @Nullable
@Override @Override
public Offer generateOffer(Transaction transaction, ContactId c, public Offer generateOffer(Transaction transaction, ContactId c,
int maxMessages, int maxLatency) throws DbException { int maxMessages, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
@@ -460,21 +505,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable @Nullable
@Override @Override
public Collection<Message> generateRequestedBatch(Transaction transaction, public Collection<Message> generateRequestedBatch(Transaction transaction,
ContactId c, int maxLength, int maxLatency) throws DbException { ContactId c, int maxLength, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency); db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
messages.add(db.getMessage(txn, m)); Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null; if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids)); transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages; return messages;
} }
@@ -692,6 +740,25 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return status; return status;
} }
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(
Transaction transaction,
ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getUnackedMessagesToSend(txn, c);
}
@Override
public long getUnackedMessageBytesToSend(Transaction transaction,
ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getUnackedMessageBytesToSend(txn, c);
}
@Override @Override
public Map<MessageId, MessageState> getMessageDependencies( public Map<MessageId, MessageState> getMessageDependencies(
Transaction transaction, MessageId m) throws DbException { Transaction transaction, MessageId m) throws DbException {
@@ -765,6 +832,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getTransportKeys(txn, t); return db.getTransportKeys(txn, t);
} }
@Override
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Transaction transaction) throws DbException {
T txn = unbox(transaction);
return db.getTransportsWithKeys(txn);
}
@Override @Override
public void incrementStreamCounter(Transaction transaction, TransportId t, public void incrementStreamCounter(Transaction transaction, TransportId t,
KeySetId k) throws DbException { KeySetId k) throws DbException {

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.db; package org.briarproject.bramble.db;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_TRANSPORT_LATENCY;
class ExponentialBackoff { class ExponentialBackoff {
/** /**
@@ -11,9 +13,11 @@ class ExponentialBackoff {
* transmissions increases exponentially. If the expiry time would * transmissions increases exponentially. If the expiry time would
* be greater than Long.MAX_VALUE, Long.MAX_VALUE is returned. * be greater than Long.MAX_VALUE, Long.MAX_VALUE is returned.
*/ */
static long calculateExpiry(long now, int maxLatency, int txCount) { static long calculateExpiry(long now, long maxLatency, int txCount) {
if (now < 0) throw new IllegalArgumentException(); if (now < 0) throw new IllegalArgumentException();
if (maxLatency <= 0) throw new IllegalArgumentException(); if (maxLatency <= 0 || maxLatency > MAX_TRANSPORT_LATENCY) {
throw new IllegalArgumentException();
}
if (txCount < 0) throw new IllegalArgumentException(); if (txCount < 0) throw new IllegalArgumentException();
// The maximum round-trip time is twice the maximum latency // The maximum round-trip time is twice the maximum latency
long roundTrip = maxLatency * 2L; long roundTrip = maxLatency * 2L;

View File

@@ -51,6 +51,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -101,7 +102,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
static final int CODE_SCHEMA_VERSION = 48; static final int CODE_SCHEMA_VERSION = 49;
// Time period offsets for incoming transport keys // Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1; private static final int OFFSET_PREV = -1;
@@ -266,7 +267,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_TRANSPORTS = private static final String CREATE_TRANSPORTS =
"CREATE TABLE transports" "CREATE TABLE transports"
+ " (transportId _STRING NOT NULL," + " (transportId _STRING NOT NULL,"
+ " maxLatency INT NOT NULL," + " maxLatency BIGINT NOT NULL,"
+ " PRIMARY KEY (transportId))"; + " PRIMARY KEY (transportId))";
private static final String CREATE_PENDING_CONTACTS = private static final String CREATE_PENDING_CONTACTS =
@@ -344,6 +345,11 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp" "CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
+ " ON statuses (contactId, timestamp)"; + " ON statuses (contactId, timestamp)";
private static final String
INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP =
"CREATE INDEX IF NOT EXISTS statusesByContactIdTxCountTimestamp"
+ " ON statuses (contactId, txCount, timestamp)";
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE = private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline" "CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)"; + " ON messages (cleanupDeadline)";
@@ -492,7 +498,8 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration44_45(), new Migration44_45(),
new Migration45_46(), new Migration45_46(),
new Migration46_47(dbTypes), new Migration46_47(dbTypes),
new Migration47_48() new Migration47_48(),
new Migration48_49()
); );
} }
@@ -570,6 +577,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID); s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE); s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
@@ -999,7 +1007,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void addTransport(Connection txn, TransportId t, int maxLatency) public void addTransport(Connection txn, TransportId t, long maxLatency)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
@@ -1120,6 +1128,55 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsAnythingToSend(Connection txn, ContactId c,
long maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND ack = TRUE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
boolean acksToSend = rs.next();
rs.close();
ps.close();
if (acksToSend) return true;
if (eager) {
sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR eta > ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsContact(Connection txn, AuthorId remote, public boolean containsContact(Connection txn, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -1277,6 +1334,29 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsTransportKeys(Connection txn, ContactId c,
TransportId t) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM outgoingKeys"
+ " WHERE contactId = ? AND transportId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setString(2, t.getString());
rs = ps.executeQuery();
boolean found = rs.next();
rs.close();
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsVisibleMessage(Connection txn, ContactId c, public boolean containsVisibleMessage(Connection txn, ContactId c,
MessageId m) throws DbException { MessageId m) throws DbException {
@@ -2109,7 +2189,7 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public Collection<MessageId> getMessagesToOffer(Connection txn, public Collection<MessageId> getMessagesToOffer(Connection txn,
ContactId c, int maxMessages, int maxLatency) throws DbException { ContactId c, int maxMessages, long maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency; long eta = now + maxLatency;
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -2168,7 +2248,7 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c, public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
int maxLength, int maxLatency) throws DbException { int maxLength, long maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency; long eta = now + maxLatency;
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -2205,6 +2285,63 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT length, messageId FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " ORDER BY txCount, timestamp";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
Map<MessageId, Integer> results = new LinkedHashMap<>();
while (rs.next()) {
int length = rs.getInt(1);
MessageId id = new MessageId(rs.getBytes(2));
results.put(id, length);
}
rs.close();
ps.close();
return results;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public long getUnackedMessageBytesToSend(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT SUM(length) FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
rs.next();
long total = rs.getInt(1);
rs.close();
ps.close();
return total;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<MessageId> getMessagesToValidate(Connection txn) public Collection<MessageId> getMessagesToValidate(Connection txn)
throws DbException { throws DbException {
@@ -2410,7 +2547,7 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public Collection<MessageId> getRequestedMessagesToSend(Connection txn, public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
ContactId c, int maxLength, int maxLatency) throws DbException { ContactId c, int maxLength, long maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long eta = now + maxLatency; long eta = now + maxLatency;
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -2574,6 +2711,38 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
String sql = "SELECT DISTINCT contactId, transportId"
+ " FROM outgoingKeys";
s = txn.createStatement();
rs = s.executeQuery(sql);
Map<ContactId, Collection<TransportId>> ids = new HashMap<>();
while (rs.next()) {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getString(2));
Collection<TransportId> transportIds = ids.get(c);
if (transportIds == null) {
transportIds = new ArrayList<>();
ids.put(c, transportIds);
}
transportIds.add(t);
}
rs.close();
s.close();
return ids;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(s, LOG, WARNING);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void incrementStreamCounter(Connection txn, TransportId t, public void incrementStreamCounter(Connection txn, TransportId t,
KeySetId k) throws DbException { KeySetId k) throws DbException {
@@ -3449,7 +3618,7 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m, public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
int maxLatency) throws DbException { long maxLatency) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {

View File

@@ -0,0 +1,41 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration48_49 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration48_49.class.getName());
@Override
public int getStartVersion() {
return 48;
}
@Override
public int getEndVersion() {
return 49;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute("ALTER TABLE transports"
+ " ALTER COLUMN maxLatency"
+ " SET DATA TYPE BIGINT");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -34,11 +35,14 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.api.system.Clock.MAX_REASONABLE_TIME_MS;
import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -52,6 +56,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock;
private final List<Service> services; private final List<Service> services;
private final List<OpenDatabaseHook> openDatabaseHooks; private final List<OpenDatabaseHook> openDatabaseHooks;
private final List<ExecutorService> executors; private final List<ExecutorService> executors;
@@ -63,9 +68,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private volatile LifecycleState state = STARTING; private volatile LifecycleState state = STARTING;
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) { LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
Clock clock) {
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock;
services = new CopyOnWriteArrayList<>(); services = new CopyOnWriteArrayList<>();
openDatabaseHooks = new CopyOnWriteArrayList<>(); openDatabaseHooks = new CopyOnWriteArrayList<>();
executors = new CopyOnWriteArrayList<>(); executors = new CopyOnWriteArrayList<>();
@@ -99,6 +106,13 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Already starting or stopping"); LOG.info("Already starting or stopping");
return ALREADY_RUNNING; return ALREADY_RUNNING;
} }
long now = clock.currentTimeMillis();
if (now < MIN_REASONABLE_TIME_MS || now > MAX_REASONABLE_TIME_MS) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("System clock is unreasonable: " + now);
}
return CLOCK_ERROR;
}
try { try {
LOG.info("Opening database"); LOG.info("Opening database");
long start = now(); long start = now();

View File

@@ -79,7 +79,8 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final Backoff backoff; private final Backoff backoff;
private final PluginCallback callback; private final PluginCallback callback;
private final int maxLatency, maxIdleTime; private final long maxLatency;
private final int maxIdleTime;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private final AtomicBoolean everConnected = new AtomicBoolean(false); private final AtomicBoolean everConnected = new AtomicBoolean(false);
@@ -121,7 +122,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
SecureRandom secureRandom, SecureRandom secureRandom,
Backoff backoff, Backoff backoff,
PluginCallback callback, PluginCallback callback,
int maxLatency, long maxLatency,
int maxIdleTime) { int maxIdleTime) {
this.connectionLimiter = connectionLimiter; this.connectionLimiter = connectionLimiter;
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
@@ -158,7 +159,7 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return maxLatency; return maxLatency;
} }
@@ -473,6 +474,16 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
return discoverSemaphore.availablePermits() == 0; return discoverSemaphore.availablePermits() == 0;
} }
@Override
public void disablePolling() {
connectionLimiter.startLimiting();
}
@Override
public void enablePolling() {
connectionLimiter.endLimiting();
}
@Override @Override
public DuplexTransportConnection discoverAndConnectForSetup(String uuid) { public DuplexTransportConnection discoverAndConnectForSetup(String uuid) {
DuplexTransportConnection conn = discoverAndConnect(uuid); DuplexTransportConnection conn = discoverAndConnect(uuid);
@@ -501,9 +512,9 @@ abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
if (s.getNamespace().equals(ID.getString())) if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings())); ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
} else if (e instanceof KeyAgreementListeningEvent) { } else if (e instanceof KeyAgreementListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementStarted); connectionLimiter.startLimiting();
} else if (e instanceof KeyAgreementStoppedListeningEvent) { } else if (e instanceof KeyAgreementStoppedListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementEnded); connectionLimiter.endLimiting();
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) { } else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
RemoteTransportPropertiesUpdatedEvent r = RemoteTransportPropertiesUpdatedEvent r =
(RemoteTransportPropertiesUpdatedEvent) e; (RemoteTransportPropertiesUpdatedEvent) e;

View File

@@ -7,14 +7,15 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
interface BluetoothConnectionLimiter { interface BluetoothConnectionLimiter {
/** /**
* Informs the limiter that key agreement has started. * Tells the limiter to not allow regular polling connections (because we
* are about to do key agreement, or connect via BT for setup).
*/ */
void keyAgreementStarted(); void startLimiting();
/** /**
* Informs the limiter that key agreement has ended. * Tells the limiter to no longer limit regular polling connections.
*/ */
void keyAgreementEnded(); void endLimiting();
/** /**
* Returns true if a contact connection can be opened. This method does not * Returns true if a contact connection can be opened. This method does not

View File

@@ -30,34 +30,37 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
private final List<DuplexTransportConnection> connections = private final List<DuplexTransportConnection> connections =
new LinkedList<>(); new LinkedList<>();
@GuardedBy("lock") @GuardedBy("lock")
private boolean keyAgreementInProgress = false; private int limitingInProgress = 0;
BluetoothConnectionLimiterImpl(EventBus eventBus) { BluetoothConnectionLimiterImpl(EventBus eventBus) {
this.eventBus = eventBus; this.eventBus = eventBus;
} }
@Override @Override
public void keyAgreementStarted() { public void startLimiting() {
synchronized (lock) { synchronized (lock) {
keyAgreementInProgress = true; limitingInProgress++;
} }
LOG.info("Key agreement started"); LOG.info("Limiting started");
eventBus.broadcast(new CloseSyncConnectionsEvent(ID)); eventBus.broadcast(new CloseSyncConnectionsEvent(ID));
} }
@Override @Override
public void keyAgreementEnded() { public void endLimiting() {
synchronized (lock) { synchronized (lock) {
keyAgreementInProgress = false; limitingInProgress--;
if (limitingInProgress < 0) {
throw new IllegalStateException();
}
} }
LOG.info("Key agreement ended"); LOG.info("Limiting ended");
} }
@Override @Override
public boolean canOpenContactConnection() { public boolean canOpenContactConnection() {
synchronized (lock) { synchronized (lock) {
if (keyAgreementInProgress) { if (limitingInProgress > 0) {
LOG.info("Can't open contact connection during key agreement"); LOG.info("Can't open contact connection while limiting");
return false; return false;
} else { } else {
LOG.info("Can open contact connection"); LOG.info("Can open contact connection");

View File

@@ -11,6 +11,10 @@ public interface BluetoothPlugin extends DuplexPlugin {
boolean isDiscovering(); boolean isDiscovering();
void disablePolling();
void enablePolling();
@Nullable @Nullable
DuplexTransportConnection discoverAndConnectForSetup(String uuid); DuplexTransportConnection discoverAndConnectForSetup(String uuid);

View File

@@ -0,0 +1,126 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.singletonMap;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_SUPPORTED;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@NotNullByDefault
abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
private static final Logger LOG =
getLogger(AbstractRemovableDrivePlugin.class.getName());
private final long maxLatency;
private final PluginCallback callback;
abstract InputStream openInputStream(TransportProperties p)
throws IOException;
abstract OutputStream openOutputStream(TransportProperties p)
throws IOException;
AbstractRemovableDrivePlugin(PluginCallback callback, long maxLatency) {
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public long getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Unused for simplex transports
throw new UnsupportedOperationException();
}
@Override
public void start() {
callback.mergeLocalProperties(
new TransportProperties(singletonMap(PROP_SUPPORTED, "true")));
}
@Override
public void stop() {
}
@Override
public State getState() {
return ACTIVE;
}
@Override
public int getReasonsDisabled() {
return 0;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public boolean isLossyAndCheap() {
return true;
}
@Override
public TransportConnectionReader createReader(TransportProperties p) {
try {
return new TransportInputStreamReader(openInputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
@Override
public TransportConnectionWriter createWriter(TransportProperties p) {
try {
return new TransportOutputStreamWriter(this, openOutputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
}

View File

@@ -15,8 +15,8 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -27,20 +27,20 @@ abstract class FilePlugin implements SimplexPlugin {
getLogger(FilePlugin.class.getName()); getLogger(FilePlugin.class.getName());
protected final PluginCallback callback; protected final PluginCallback callback;
protected final int maxLatency; protected final long maxLatency;
protected abstract void writerFinished(File f, boolean exception); protected abstract void writerFinished(File f, boolean exception);
protected abstract void readerFinished(File f, boolean exception, protected abstract void readerFinished(File f, boolean exception,
boolean recognised); boolean recognised);
FilePlugin(PluginCallback callback, int maxLatency) { FilePlugin(PluginCallback callback, long maxLatency) {
this.callback = callback; this.callback = callback;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return maxLatency; return maxLatency;
} }

View File

@@ -27,7 +27,7 @@ class FileTransportWriter implements TransportConnectionWriter {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }
@@ -36,6 +36,11 @@ class FileTransportWriter implements TransportConnectionWriter {
return plugin.getMaxIdleTime(); return plugin.getMaxIdleTime();
} }
@Override
public boolean isLossyAndCheap() {
return plugin.isLossyAndCheap();
}
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return out; return out;

View File

@@ -0,0 +1,125 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.transport.KeyManager;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_SUPPORTED;
import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY;
@ThreadSafe
@NotNullByDefault
class RemovableDriveManagerImpl
implements RemovableDriveManager, RemovableDriveTaskRegistry {
private final Executor ioExecutor;
private final DatabaseComponent db;
private final KeyManager keyManager;
private final TransportPropertyManager transportPropertyManager;
private final RemovableDriveTaskFactory taskFactory;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private RemovableDriveTask reader = null;
@GuardedBy("lock")
@Nullable
private RemovableDriveTask writer = null;
@Inject
RemovableDriveManagerImpl(
@IoExecutor Executor ioExecutor,
DatabaseComponent db,
KeyManager keyManager,
TransportPropertyManager transportPropertyManager,
RemovableDriveTaskFactory taskFactory) {
this.ioExecutor = ioExecutor;
this.db = db;
this.keyManager = keyManager;
this.transportPropertyManager = transportPropertyManager;
this.taskFactory = taskFactory;
}
@Nullable
@Override
public RemovableDriveTask getCurrentReaderTask() {
synchronized (lock) {
return reader;
}
}
@Nullable
@Override
public RemovableDriveTask getCurrentWriterTask() {
synchronized (lock) {
return writer;
}
}
@Override
public RemovableDriveTask startReaderTask(TransportProperties p) {
RemovableDriveTask created;
synchronized (lock) {
if (reader != null) return reader;
reader = created = taskFactory.createReader(this, p);
}
ioExecutor.execute(created);
return created;
}
@Override
public RemovableDriveTask startWriterTask(ContactId c,
TransportProperties p) {
RemovableDriveTask created;
synchronized (lock) {
if (writer != null) return writer;
writer = created = taskFactory.createWriter(this, c, p);
}
ioExecutor.execute(created);
return created;
}
@Override
public boolean isTransportSupportedByContact(ContactId c)
throws DbException {
if (!keyManager.canSendOutgoingStreams(c, ID)) return false;
TransportProperties p =
transportPropertyManager.getRemoteProperties(c, ID);
return "true".equals(p.get(PROP_SUPPORTED));
}
@Override
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
return db.transactionWithResult(true, txn ->
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
}
@Override
public void removeReader(RemovableDriveTask task) {
synchronized (lock) {
if (reader == task) reader = null;
}
}
@Override
public void removeWriter(RemovableDriveTask task) {
synchronized (lock) {
if (writer == task) writer = null;
}
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class RemovableDriveModule {
@Provides
@Singleton
RemovableDriveManager provideRemovableDriveManager(
RemovableDriveManagerImpl removableDriveManager) {
return removableDriveManager;
}
@Provides
RemovableDriveTaskFactory provideTaskFactory(
RemovableDriveTaskFactoryImpl taskFactory) {
return taskFactory;
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_PATH;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class RemovableDrivePlugin extends AbstractRemovableDrivePlugin {
RemovableDrivePlugin(PluginCallback callback, long maxLatency) {
super(callback, maxLatency);
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileInputStream(path);
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileOutputStream(path);
}
}

View File

@@ -0,0 +1,41 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
static final long MAX_LATENCY = DAYS.toMillis(28);
@Inject
RemovableDrivePluginFactory() {
}
@Override
public TransportId getId() {
return ID;
}
@Override
public long getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new RemovableDrivePlugin(callback, MAX_LATENCY);
}
}

View File

@@ -0,0 +1,69 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@NotNullByDefault
class RemovableDriveReaderTask extends RemovableDriveTaskImpl {
private final static Logger LOG =
getLogger(RemovableDriveReaderTask.class.getName());
RemovableDriveReaderTask(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, transportProperties);
}
@Override
public void run() {
TransportConnectionReader r =
getPlugin().createReader(transportProperties);
if (r == null) {
LOG.warning("Failed to create reader");
registry.removeReader(this);
setSuccess(false);
return;
}
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
}
private class DecoratedReader implements TransportConnectionReader {
private final TransportConnectionReader delegate;
private DecoratedReader(TransportConnectionReader delegate) {
this.delegate = delegate;
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public void dispose(boolean exception, boolean recognised)
throws IOException {
delegate.dispose(exception, recognised);
registry.removeReader(RemovableDriveReaderTask.this);
setSuccess(!exception && recognised);
}
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
interface RemovableDriveTaskFactory {
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
TransportProperties p);
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p);
}

View File

@@ -0,0 +1,55 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory {
private final DatabaseComponent db;
private final Executor eventExecutor;
private final PluginManager pluginManager;
private final ConnectionManager connectionManager;
private final EventBus eventBus;
@Inject
RemovableDriveTaskFactoryImpl(
DatabaseComponent db,
@EventExecutor Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus) {
this.db = db;
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
}
@Override
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
TransportProperties p) {
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
connectionManager, eventBus, registry, p);
}
@Override
public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p) {
return new RemovableDriveWriterTask(db, eventExecutor, pluginManager,
connectionManager, eventBus, registry, c, p);
}
}

View File

@@ -0,0 +1,114 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Math.min;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@ThreadSafe
@NotNullByDefault
abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
private final Executor eventExecutor;
private final PluginManager pluginManager;
final ConnectionManager connectionManager;
final EventBus eventBus;
final RemovableDriveTaskRegistry registry;
final TransportProperties transportProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<Consumer<State>> observers = new ArrayList<>();
@GuardedBy("lock")
private State state = new State(0, 0, false, false);
RemovableDriveTaskImpl(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
TransportProperties transportProperties) {
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
this.registry = registry;
this.transportProperties = transportProperties;
}
@Override
public TransportProperties getTransportProperties() {
return transportProperties;
}
@Override
public void addObserver(Consumer<State> o) {
State state;
synchronized (lock) {
observers.add(o);
state = this.state;
eventExecutor.execute(() -> o.accept(state));
}
}
@Override
public void removeObserver(Consumer<State> o) {
synchronized (lock) {
observers.remove(o);
}
}
SimplexPlugin getPlugin() {
return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
}
void setTotal(long total) {
synchronized (lock) {
state = new State(state.getDone(), total, state.isFinished(),
state.isSuccess());
notifyObservers();
}
}
void addDone(long done) {
synchronized (lock) {
// Done and total come from different sources; make them consistent
done = min(state.getDone() + done, state.getTotal());
state = new State(done, state.getTotal(), state.isFinished(),
state.isSuccess());
notifyObservers();
}
}
void setSuccess(boolean success) {
synchronized (lock) {
state = new State(state.getDone(), state.getTotal(), true, success);
notifyObservers();
}
}
@GuardedBy("lock")
private void notifyObservers() {
List<Consumer<State>> observers = new ArrayList<>(this.observers);
State state = this.state;
eventExecutor.execute(() -> {
for (Consumer<State> o : observers) o.accept(state);
});
}
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
@NotNullByDefault
interface RemovableDriveTaskRegistry {
void removeReader(RemovableDriveTask task);
void removeWriter(RemovableDriveTask task);
}

View File

@@ -0,0 +1,126 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class RemovableDriveWriterTask extends RemovableDriveTaskImpl
implements EventListener {
private static final Logger LOG =
getLogger(RemovableDriveWriterTask.class.getName());
private final DatabaseComponent db;
private final ContactId contactId;
RemovableDriveWriterTask(
DatabaseComponent db,
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
ContactId contactId,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, transportProperties);
this.db = db;
this.contactId = contactId;
}
@Override
public void run() {
SimplexPlugin plugin = getPlugin();
TransportConnectionWriter w = plugin.createWriter(transportProperties);
if (w == null) {
LOG.warning("Failed to create writer");
registry.removeWriter(this);
setSuccess(false);
return;
}
try {
setTotal(db.transactionWithResult(true, txn ->
db.getUnackedMessageBytesToSend(txn, contactId)));
} catch (DbException e) {
logException(LOG, WARNING, e);
registry.removeWriter(this);
setSuccess(false);
return;
}
eventBus.addListener(this);
connectionManager.manageOutgoingConnection(contactId, ID,
new DecoratedWriter(w));
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessagesSentEvent) {
MessagesSentEvent m = (MessagesSentEvent) e;
if (contactId.equals(m.getContactId())) {
if (LOG.isLoggable(INFO)) {
LOG.info(m.getMessageIds().size() + " messages sent");
}
addDone(m.getTotalLength());
}
}
}
private class DecoratedWriter implements TransportConnectionWriter {
private final TransportConnectionWriter delegate;
private DecoratedWriter(TransportConnectionWriter delegate) {
this.delegate = delegate;
}
@Override
public long getMaxLatency() {
return delegate.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return delegate.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return delegate.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public void dispose(boolean exception) throws IOException {
delegate.dispose(exception);
registry.removeWriter(RemovableDriveWriterTask.this);
eventBus.removeListener(RemovableDriveWriterTask.this);
setSuccess(!exception);
}
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import java.io.InputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportInputStreamReader implements TransportConnectionReader {
private static final Logger LOG =
getLogger(TransportInputStreamReader.class.getName());
private final InputStream in;
TransportInputStreamReader(InputStream in) {
this.in = in;
}
@Override
public InputStream getInputStream() {
return in;
}
@Override
public void dispose(boolean exception, boolean recognised) {
tryToClose(in, LOG, WARNING);
}
}

View File

@@ -0,0 +1,52 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportOutputStreamWriter implements TransportConnectionWriter {
private static final Logger LOG =
getLogger(TransportOutputStreamWriter.class.getName());
private final SimplexPlugin plugin;
private final OutputStream out;
TransportOutputStreamWriter(SimplexPlugin plugin, OutputStream out) {
this.plugin = plugin;
this.out = out;
}
@Override
public long getMaxLatency() {
return plugin.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return plugin.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return plugin.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public void dispose(boolean exception) {
tryToClose(out, LOG, WARNING);
}
}

View File

@@ -92,7 +92,7 @@ class LanTcpPlugin extends TcpPlugin {
Executor wakefulIoExecutor, Executor wakefulIoExecutor,
Backoff backoff, Backoff backoff,
PluginCallback callback, PluginCallback callback,
int maxLatency, long maxLatency,
int maxIdleTime, int maxIdleTime,
int connectionTimeout) { int connectionTimeout) {
super(ioExecutor, wakefulIoExecutor, backoff, callback, maxLatency, super(ioExecutor, wakefulIoExecutor, backoff, callback, maxLatency,

View File

@@ -50,7 +50,7 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return MAX_LATENCY; return MAX_LATENCY;
} }

View File

@@ -69,8 +69,8 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
protected final Executor ioExecutor, wakefulIoExecutor, bindExecutor; protected final Executor ioExecutor, wakefulIoExecutor, bindExecutor;
protected final Backoff backoff; protected final Backoff backoff;
protected final PluginCallback callback; protected final PluginCallback callback;
protected final int maxLatency, maxIdleTime; protected final long maxLatency;
protected final int connectionTimeout, socketTimeout; protected final int maxIdleTime, connectionTimeout, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false); protected final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState(); protected final PluginState state = new PluginState();
@@ -111,7 +111,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
Executor wakefulIoExecutor, Executor wakefulIoExecutor,
Backoff backoff, Backoff backoff,
PluginCallback callback, PluginCallback callback,
int maxLatency, long maxLatency,
int maxIdleTime, int maxIdleTime,
int connectionTimeout) { int connectionTimeout) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -129,7 +129,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return maxLatency; return maxLatency;
} }

View File

@@ -35,7 +35,7 @@ class WanTcpPlugin extends TcpPlugin {
Backoff backoff, Backoff backoff,
PortMapper portMapper, PortMapper portMapper,
PluginCallback callback, PluginCallback callback,
int maxLatency, long maxLatency,
int maxIdleTime, int maxIdleTime,
int connectionTimeout) { int connectionTimeout) {
super(ioExecutor, wakefulIoExecutor, backoff, callback, maxLatency, super(ioExecutor, wakefulIoExecutor, backoff, callback, maxLatency,

View File

@@ -54,7 +54,7 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return MAX_LATENCY; return MAX_LATENCY;
} }

View File

@@ -131,7 +131,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final String architecture; private final String architecture;
private final CircumventionProvider circumventionProvider; private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider; private final ResourceProvider resourceProvider;
private final int maxLatency, maxIdleTime, socketTimeout; private final long maxLatency;
private final int maxIdleTime, socketTimeout;
private final File torDirectory, geoIpFile, configFile; private final File torDirectory, geoIpFile, configFile;
private final File doneFile, cookieFile; private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@@ -159,7 +160,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
TorRendezvousCrypto torRendezvousCrypto, TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback, PluginCallback callback,
String architecture, String architecture,
int maxLatency, long maxLatency,
int maxIdleTime, int maxIdleTime,
File torDirectory) { File torDirectory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -204,7 +205,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public int getMaxLatency() { public long getMaxLatency() {
return maxLatency; return maxLatency;
} }

View File

@@ -44,6 +44,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable @Immutable
@@ -115,8 +116,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
try { try {
// Find the latest update for this transport, if any // Find the latest update for this transport, if any
BdfDictionary d = metadataParser.parse(meta); BdfDictionary d = metadataParser.parse(meta);
@@ -131,14 +132,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
// We've already received a newer update - delete this one // We've already received a newer update - delete this one
db.deleteMessage(txn, m.getId()); db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId());
return false; return ACCEPT_DO_NOT_SHARE;
} }
} }
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t)); txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
return false; return ACCEPT_DO_NOT_SHARE;
} }
@Override @Override

View File

@@ -77,7 +77,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final Clock clock; private final Clock clock;
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final int maxLatency, maxIdleTime; private final long maxLatency, maxIdleTime;
private final StreamWriter streamWriter; private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter; private final SyncRecordWriter recordWriter;
@Nullable @Nullable
@@ -95,7 +95,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, Clock clock, ContactId contactId, EventBus eventBus, Clock clock, ContactId contactId,
TransportId transportId, int maxLatency, int maxIdleTime, TransportId transportId, long maxLatency, int maxIdleTime,
StreamWriter streamWriter, SyncRecordWriter recordWriter, StreamWriter streamWriter, SyncRecordWriter recordWriter,
@Nullable Priority priority) { @Nullable Priority priority) {
this.db = db; this.db = db;

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.SyncRecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions; import org.briarproject.bramble.api.sync.Versions;
@@ -22,7 +23,11 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@@ -60,7 +65,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final EventBus eventBus; private final EventBus eventBus;
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final int maxLatency; private final long maxLatency;
private final boolean eager;
private final StreamWriter streamWriter; private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter; private final SyncRecordWriter recordWriter;
private final AtomicInteger outstandingQueries; private final AtomicInteger outstandingQueries;
@@ -70,7 +76,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, TransportId transportId, EventBus eventBus, ContactId contactId, TransportId transportId,
int maxLatency, StreamWriter streamWriter, long maxLatency, boolean eager, StreamWriter streamWriter,
SyncRecordWriter recordWriter) { SyncRecordWriter recordWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
@@ -78,6 +84,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.eager = eager;
this.streamWriter = streamWriter; this.streamWriter = streamWriter;
this.recordWriter = recordWriter; this.recordWriter = recordWriter;
outstandingQueries = new AtomicInteger(2); // One per type of record outstandingQueries = new AtomicInteger(2); // One per type of record
@@ -92,8 +99,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
// Send our supported protocol versions // Send our supported protocol versions
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS)); recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
// Start a query for each type of record // Start a query for each type of record
dbExecutor.execute(new GenerateAck()); dbExecutor.execute(this::generateAck);
dbExecutor.execute(new GenerateBatch()); if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
else dbExecutor.execute(this::generateBatch);
// Write records until interrupted or no more records to write // Write records until interrupted or no more records to write
try { try {
while (!interrupted) { while (!interrupted) {
@@ -138,81 +146,110 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
} }
} }
private class GenerateAck implements Runnable { @DatabaseExecutor
private void loadUnackedMessageIds() {
@DatabaseExecutor if (interrupted) return;
@Override try {
public void run() { Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
if (interrupted) return; db.getUnackedMessagesToSend(txn, contactId));
try { if (LOG.isLoggable(INFO)) {
Ack a = db.transactionWithNullableResult(false, txn -> LOG.info(ids.size() + " unacked messages to send");
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if (a == null) decrementOutstandingQueries();
else writerTasks.add(new WriteAck(a));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
if (ids.isEmpty()) decrementOutstandingQueries();
else dbExecutor.execute(() -> generateEagerBatch(ids));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
} }
private class WriteAck implements ThrowingRunnable<IOException> { @DatabaseExecutor
private void generateEagerBatch(Map<MessageId, Integer> ids) {
private final Ack ack; if (interrupted) return;
// Take some message IDs from `ids` to form a batch
private WriteAck(Ack ack) { Collection<MessageId> batchIds = new ArrayList<>();
this.ack = ack; long totalLength = 0;
Iterator<Entry<MessageId, Integer>> it = ids.entrySet().iterator();
while (it.hasNext()) {
// Check whether the next message will fit in the batch
Entry<MessageId, Integer> e = it.next();
int length = e.getValue();
if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break;
// Add the message to the batch
it.remove();
batchIds.add(e.getKey());
totalLength += length;
} }
if (batchIds.isEmpty()) throw new AssertionError();
@IoExecutor try {
@Override Collection<Message> batch =
public void run() throws IOException { db.transactionWithResult(false, txn ->
if (interrupted) return; db.generateBatch(txn, contactId, batchIds,
recordWriter.writeAck(ack); maxLatency));
LOG.info("Sent ack"); writerTasks.add(() -> writeEagerBatch(batch, ids));
dbExecutor.execute(new GenerateAck()); } catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
} }
private class GenerateBatch implements Runnable { @IoExecutor
private void writeEagerBatch(Collection<Message> batch,
Map<MessageId, Integer> ids) throws IOException {
if (interrupted) return;
for (Message m : batch) recordWriter.writeMessage(m);
LOG.info("Sent eager batch");
if (ids.isEmpty()) decrementOutstandingQueries();
else dbExecutor.execute(() -> generateEagerBatch(ids));
}
@DatabaseExecutor @DatabaseExecutor
@Override private void generateAck() {
public void run() { if (interrupted) return;
if (interrupted) return; try {
try { Ack a = db.transactionWithNullableResult(false, txn ->
Collection<Message> b = db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
db.transactionWithNullableResult(false, txn -> if (LOG.isLoggable(INFO))
db.generateBatch(txn, contactId, LOG.info("Generated ack: " + (a != null));
MAX_RECORD_PAYLOAD_BYTES, maxLatency)); if (a == null) decrementOutstandingQueries();
if (LOG.isLoggable(INFO)) else writerTasks.add(() -> writeAck(a));
LOG.info("Generated batch: " + (b != null)); } catch (DbException e) {
if (b == null) decrementOutstandingQueries(); logException(LOG, WARNING, e);
else writerTasks.add(new WriteBatch(b)); interrupt();
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
}
} }
} }
private class WriteBatch implements ThrowingRunnable<IOException> { @IoExecutor
private void writeAck(Ack ack) throws IOException {
if (interrupted) return;
recordWriter.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(this::generateAck);
}
private final Collection<Message> batch; @DatabaseExecutor
private void generateBatch() {
private WriteBatch(Collection<Message> batch) { if (interrupted) return;
this.batch = batch; try {
Collection<Message> b =
db.transactionWithNullableResult(false, txn ->
db.generateBatch(txn, contactId,
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null));
if (b == null) decrementOutstandingQueries();
else writerTasks.add(() -> writeBatch(b));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
}
@IoExecutor @IoExecutor
@Override private void writeBatch(Collection<Message> batch) throws IOException {
public void run() throws IOException { if (interrupted) return;
if (interrupted) return; for (Message m : batch) recordWriter.writeMessage(m);
for (Message m : batch) recordWriter.writeMessage(m); LOG.info("Sent batch");
LOG.info("Sent batch"); dbExecutor.execute(this::generateBatch);
dbExecutor.execute(new GenerateBatch());
}
} }
} }

View File

@@ -60,17 +60,17 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
@Override @Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter) { long maxLatency, boolean eager, StreamWriter streamWriter) {
OutputStream out = streamWriter.getOutputStream(); OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter = SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out); recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t, return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
maxLatency, streamWriter, recordWriter); maxLatency, eager, streamWriter, recordWriter);
} }
@Override @Override
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, StreamWriter streamWriter, long maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority) { @Nullable Priority priority) {
OutputStream out = streamWriter.getOutputStream(); OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter = SyncRecordWriter recordWriter =

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.sync.validation.MessageValidator; import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.api.sync.validation.ValidationManager; import org.briarproject.bramble.api.sync.validation.ValidationManager;
@@ -40,6 +41,10 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
@@ -185,16 +190,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
int majorVersion = g.getMajorVersion(); int majorVersion = g.getMajorVersion();
Metadata meta = Metadata meta =
db.getMessageMetadataForValidator(txn, id); db.getMessageMetadataForValidator(txn, id);
DeliveryResult result = DeliveryAction action =
deliverMessage(txn, m, c, majorVersion, meta); deliverMessage(txn, m, c, majorVersion, meta);
if (result.valid) { if (action == REJECT) {
addPendingDependents(txn, id, pending); invalidateMessage(txn, id);
if (result.share) {
db.setMessageShared(txn, id);
toShare.addAll(states.keySet());
}
} else {
addDependentsToInvalidate(txn, id, invalidate); addDependentsToInvalidate(txn, id, invalidate);
} else if (action == ACCEPT_SHARE) {
db.setMessageState(txn, m.getId(), DELIVERED);
addPendingDependents(txn, id, pending);
db.setMessageShared(txn, id);
toShare.addAll(states.keySet());
} else if (action == ACCEPT_DO_NOT_SHARE) {
db.setMessageState(txn, m.getId(), DELIVERED);
addPendingDependents(txn, id, pending);
} }
} }
} }
@@ -275,16 +283,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
Metadata meta = context.getMetadata(); Metadata meta = context.getMetadata();
db.mergeMessageMetadata(txn, id, meta); db.mergeMessageMetadata(txn, id, meta);
if (allDelivered) { if (allDelivered) {
DeliveryResult result = DeliveryAction action =
deliverMessage(txn, m, c, majorVersion, meta); deliverMessage(txn, m, c, majorVersion, meta);
if (result.valid) { if (action == REJECT) {
addPendingDependents(txn, id, pending); invalidateMessage(txn, id);
if (result.share) {
db.setMessageShared(txn, id);
toShare.addAll(dependencies);
}
} else {
addDependentsToInvalidate(txn, id, invalidate); addDependentsToInvalidate(txn, id, invalidate);
} else if (action == DEFER) {
db.setMessageState(txn, id, PENDING);
} else if (action == ACCEPT_SHARE) {
db.setMessageState(txn, id, DELIVERED);
addPendingDependents(txn, id, pending);
db.setMessageShared(txn, id);
toShare.addAll(dependencies);
} else if (action == ACCEPT_DO_NOT_SHARE) {
db.setMessageState(txn, id, DELIVERED);
addPendingDependents(txn, id, pending);
} }
} else { } else {
db.setMessageState(txn, id, PENDING); db.setMessageState(txn, id, PENDING);
@@ -304,23 +317,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
@DatabaseExecutor @DatabaseExecutor
private DeliveryResult deliverMessage(Transaction txn, Message m, private DeliveryAction deliverMessage(Transaction txn, Message m,
ClientId c, int majorVersion, Metadata meta) throws DbException { ClientId c, int majorVersion, Metadata meta) {
// Deliver the message to the client if it's registered a hook // Deliver the message to the client if it has registered a hook
boolean shareMsg = false;
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion); ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
IncomingMessageHook hook = hooks.get(cv); IncomingMessageHook hook = hooks.get(cv);
if (hook != null) { if (hook == null) return ACCEPT_DO_NOT_SHARE;
try { try {
shareMsg = hook.incomingMessage(txn, m, meta); return hook.incomingMessage(txn, m, meta);
} catch (InvalidMessageException e) { } catch (DbException e) {
logException(LOG, INFO, e); logException(LOG, INFO, e);
invalidateMessage(txn, m.getId()); return DEFER;
return new DeliveryResult(false, false); } catch (InvalidMessageException e) {
} logException(LOG, INFO, e);
return REJECT;
} }
db.setMessageState(txn, m.getId(), DELIVERED);
return new DeliveryResult(true, shareMsg);
} }
@DatabaseExecutor @DatabaseExecutor
@@ -447,14 +458,4 @@ class ValidationManagerImpl implements ValidationManager, Service,
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
private static class DeliveryResult {
private final boolean valid, share;
private DeliveryResult(boolean valid, boolean share) {
this.valid = valid;
this.share = share;
}
}
} }

View File

@@ -19,9 +19,8 @@ import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginFactory;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
@@ -40,6 +39,7 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_TRANSPORT_LATENCY;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -51,7 +51,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
private final PluginConfig pluginConfig; private final PluginConfig pluginConfig;
private final TransportKeyManagerFactory transportKeyManagerFactory;
private final TransportCrypto transportCrypto; private final TransportCrypto transportCrypto;
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers; private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
@@ -61,34 +60,35 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
KeyManagerImpl(DatabaseComponent db, KeyManagerImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
PluginConfig pluginConfig, PluginConfig pluginConfig,
TransportKeyManagerFactory transportKeyManagerFactory, TransportCrypto transportCrypto,
TransportCrypto transportCrypto) { TransportKeyManagerFactory transportKeyManagerFactory) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.pluginConfig = pluginConfig; this.pluginConfig = pluginConfig;
this.transportKeyManagerFactory = transportKeyManagerFactory;
this.transportCrypto = transportCrypto; this.transportCrypto = transportCrypto;
managers = new ConcurrentHashMap<>(); managers = new ConcurrentHashMap<>();
for (PluginFactory<?> f : pluginConfig.getSimplexFactories()) {
TransportKeyManager m = transportKeyManagerFactory.
createTransportKeyManager(f.getId(), f.getMaxLatency());
managers.put(f.getId(), m);
}
for (PluginFactory<?> f : pluginConfig.getDuplexFactories()) {
TransportKeyManager m = transportKeyManagerFactory.
createTransportKeyManager(f.getId(), f.getMaxLatency());
managers.put(f.getId(), m);
}
} }
@Override @Override
public void startService() throws ServiceException { public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
Map<TransportId, Integer> transports = new HashMap<>();
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
transports.put(f.getId(), f.getMaxLatency());
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
transports.put(f.getId(), f.getMaxLatency());
try { try {
db.transaction(false, txn -> { db.transaction(false, txn -> {
for (Entry<TransportId, Integer> e : transports.entrySet()) for (PluginFactory<?> f : pluginConfig.getSimplexFactories()) {
db.addTransport(txn, e.getKey(), e.getValue()); addTransport(txn, f);
for (Entry<TransportId, Integer> e : transports.entrySet()) { }
TransportKeyManager m = transportKeyManagerFactory for (PluginFactory<?> f : pluginConfig.getDuplexFactories()) {
.createTransportKeyManager(e.getKey(), addTransport(txn, f);
e.getValue());
managers.put(e.getKey(), m);
m.start(txn);
} }
}); });
} catch (DbException e) { } catch (DbException e) {
@@ -96,14 +96,32 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
} }
private void addTransport(Transaction txn, PluginFactory<?> f)
throws DbException {
long maxLatency = f.getMaxLatency();
if (maxLatency > MAX_TRANSPORT_LATENCY) {
throw new IllegalStateException();
}
db.addTransport(txn, f.getId(), maxLatency);
managers.get(f.getId()).start(txn);
}
@Override @Override
public void stopService() { public void stopService() {
} }
@Override @Override
public Map<TransportId, KeySetId> addRotationKeys( public KeySetId addRotationKeys(Transaction txn, ContactId c,
Transaction txn, ContactId c, SecretKey rootKey, long timestamp, TransportId t, SecretKey rootKey, long timestamp, boolean alice,
boolean alice, boolean active) throws DbException { boolean active) throws DbException {
return withManager(t, m ->
m.addRotationKeys(txn, c, rootKey, timestamp, alice, active));
}
@Override
public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
@@ -137,7 +155,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair) PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException { throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto SecretKey staticMasterKey = transportCrypto
.deriveStaticMasterKey(theirPublicKey, ourKeyPair); .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
SecretKey rootKey = SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true); transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair); boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);

View File

@@ -0,0 +1,22 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
@NotNullByDefault
interface MessageEncoder {
Message encodeKeyMessage(GroupId contactGroupId,
TransportId transportId, PublicKey publicKey);
Message encodeActivateMessage(GroupId contactGroupId,
TransportId transportId, MessageId previousMessageId);
BdfDictionary encodeMessageMetadata(TransportId transportId,
MessageType type, boolean local);
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
@Immutable
@NotNullByDefault
class MessageEncoderImpl implements MessageEncoder {
private final ClientHelper clientHelper;
private final Clock clock;
@Inject
MessageEncoderImpl(ClientHelper clientHelper, Clock clock) {
this.clientHelper = clientHelper;
this.clock = clock;
}
@Override
public Message encodeKeyMessage(GroupId contactGroupId,
TransportId transportId, PublicKey publicKey) {
BdfList body = BdfList.of(
KEY.getValue(),
transportId.getString(),
publicKey.getEncoded());
return encodeMessage(contactGroupId, body);
}
@Override
public Message encodeActivateMessage(GroupId contactGroupId,
TransportId transportId, MessageId previousMessageId) {
BdfList body = BdfList.of(
ACTIVATE.getValue(),
transportId.getString(),
previousMessageId);
return encodeMessage(contactGroupId, body);
}
@Override
public BdfDictionary encodeMessageMetadata(TransportId transportId,
MessageType type, boolean local) {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_IS_SESSION, false),
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()),
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()),
new BdfEntry(MSG_KEY_LOCAL, local));
}
private Message encodeMessage(GroupId contactGroupId, BdfList body) {
try {
return clientHelper.createMessage(contactGroupId,
clock.currentTimeMillis(), clientHelper.toByteArray(body));
} catch (FormatException e) {
throw new AssertionError();
}
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum MessageType {
KEY(0),
ACTIVATE(1);
private final int value;
MessageType(int value) {
this.value = value;
}
int getValue() {
return value;
}
static MessageType fromValue(int value) throws FormatException {
for (MessageType t : values()) if (t.value == value) return t;
throw new FormatException();
}
}

View File

@@ -0,0 +1,58 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class Session {
private final State state;
@Nullable
private final MessageId lastLocalMessageId;
@Nullable
private final KeyPair localKeyPair;
@Nullable
private final Long localTimestamp;
@Nullable
private final KeySetId keySetId;
Session(State state, @Nullable MessageId lastLocalMessageId,
@Nullable KeyPair localKeyPair, @Nullable Long localTimestamp,
@Nullable KeySetId keySetId) {
this.state = state;
this.lastLocalMessageId = lastLocalMessageId;
this.localKeyPair = localKeyPair;
this.localTimestamp = localTimestamp;
this.keySetId = keySetId;
}
State getState() {
return state;
}
@Nullable
MessageId getLastLocalMessageId() {
return lastLocalMessageId;
}
@Nullable
KeyPair getLocalKeyPair() {
return localKeyPair;
}
@Nullable
Long getLocalTimestamp() {
return localTimestamp;
}
@Nullable
KeySetId getKeySetId() {
return keySetId;
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@NotNullByDefault
interface SessionEncoder {
BdfDictionary encodeSession(Session s, TransportId transportId);
BdfDictionary getSessionQuery(TransportId transportId);
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionEncoderImpl implements SessionEncoder {
@Inject
SessionEncoderImpl() {
}
@Override
public BdfDictionary encodeSession(Session s, TransportId transportId) {
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_IS_SESSION, true);
meta.put(MSG_KEY_TRANSPORT_ID, transportId.getString());
meta.put(SESSION_KEY_STATE, s.getState().getValue());
putNullable(meta, SESSION_KEY_LAST_LOCAL_MESSAGE_ID,
s.getLastLocalMessageId());
KeyPair localKeyPair = s.getLocalKeyPair();
if (localKeyPair == null) {
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, NULL_VALUE);
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, NULL_VALUE);
} else {
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY,
localKeyPair.getPublic().getEncoded());
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY,
localKeyPair.getPrivate().getEncoded());
}
putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
KeySetId keySetId = s.getKeySetId();
if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE);
else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt());
return meta;
}
@Override
public BdfDictionary getSessionQuery(TransportId transportId) {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_IS_SESSION, true),
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()));
}
private void putNullable(BdfDictionary meta, String key,
@Nullable Object o) {
meta.put(key, o == null ? NULL_VALUE : o);
}
}

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SessionParser {
Session parseSession(BdfDictionary meta) throws FormatException;
}

View File

@@ -0,0 +1,67 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionParserImpl implements SessionParser {
private final TransportKeyAgreementCrypto crypto;
@Inject
SessionParserImpl(TransportKeyAgreementCrypto crypto) {
this.crypto = crypto;
}
@Override
public Session parseSession(BdfDictionary meta) throws FormatException {
State state =
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
MessageId lastLocalMessageId = null;
byte[] lastLocalMessageIdBytes =
meta.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
if (lastLocalMessageIdBytes != null) {
lastLocalMessageId = new MessageId(lastLocalMessageIdBytes);
}
KeyPair localKeyPair = null;
byte[] localPublicKeyBytes =
meta.getOptionalRaw(SESSION_KEY_LOCAL_PUBLIC_KEY);
byte[] localPrivateKeyBytes =
meta.getOptionalRaw(SESSION_KEY_LOCAL_PRIVATE_KEY);
if (localPublicKeyBytes != null && localPrivateKeyBytes != null) {
PublicKey pub = crypto.parsePublicKey(localPublicKeyBytes);
PrivateKey priv = crypto.parsePrivateKey(localPrivateKeyBytes);
localKeyPair = new KeyPair(pub, priv);
}
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
KeySetId keySetId = null;
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
if (keySetIdLong != null) {
keySetId = new KeySetId(keySetIdLong.intValue());
}
return new Session(state, lastLocalMessageId, localKeyPair,
localTimestamp, keySetId);
}
}

View File

@@ -0,0 +1,43 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum State {
/**
* We've sent a key message and are awaiting the contact's key message.
*/
AWAIT_KEY(0),
/**
* We've exchanged key messages, derived the transport keys and sent an
* activate message, and now we're awaiting the contact's activate message.
*/
AWAIT_ACTIVATE(1),
/**
* We've exchanged key messages and activate messages, and have derived and
* activated the transport keys. This is the end state.
*/
ACTIVATED(2);
private final int value;
State(int value) {
this.value = value;
}
int getValue() {
return value;
}
static State fromValue(int value) throws FormatException {
for (State s : values()) if (s.value == value) return s;
throw new FormatException();
}
}

View File

@@ -0,0 +1,27 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface TransportKeyAgreementConstants {
String MSG_KEY_IS_SESSION = "isSession";
String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_TRANSPORT_ID = "transportId";
String MSG_KEY_PUBLIC_KEY = "publicKey";
String MSG_KEY_LOCAL = "local";
String SESSION_KEY_STATE = "state";
String SESSION_KEY_LAST_LOCAL_MESSAGE_ID = "lastLocalMessageId";
String SESSION_KEY_LOCAL_PUBLIC_KEY = "localPublicKey";
String SESSION_KEY_LOCAL_PRIVATE_KEY = "localPrivateKey";
String SESSION_KEY_LOCAL_TIMESTAMP = "localTimestamp";
String SESSION_KEY_KEY_SET_ID = "keySetId";
/**
* Label for deriving the root key from key pairs.
*/
String ROOT_KEY_LABEL =
"org.briarproject.bramble.transport.agreement/ROOT_KEY";
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
@NotNullByDefault
interface TransportKeyAgreementCrypto {
KeyPair generateKeyPair();
SecretKey deriveRootKey(KeyPair localKeyPair, PublicKey remotePublicKey)
throws GeneralSecurityException;
PublicKey parsePublicKey(byte[] encoded) throws FormatException;
PrivateKey parsePrivateKey(byte[] encoded) throws FormatException;
}

View File

@@ -0,0 +1,66 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.Bytes.compare;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.ROOT_KEY_LABEL;
@Immutable
@NotNullByDefault
class TransportKeyAgreementCryptoImpl implements TransportKeyAgreementCrypto {
private final CryptoComponent crypto;
@Inject
TransportKeyAgreementCryptoImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
@Override
public KeyPair generateKeyPair() {
return crypto.generateAgreementKeyPair();
}
@Override
public SecretKey deriveRootKey(KeyPair localKeyPair,
PublicKey remotePublicKey) throws GeneralSecurityException {
byte[] theirPublic = remotePublicKey.getEncoded();
byte[] ourPublic = localKeyPair.getPublic().getEncoded();
boolean alice = compare(ourPublic, theirPublic) < 0;
byte[][] inputs = {
alice ? ourPublic : theirPublic,
alice ? theirPublic : ourPublic
};
return crypto.deriveSharedSecret(ROOT_KEY_LABEL, remotePublicKey,
localKeyPair, inputs);
}
@Override
public PublicKey parsePublicKey(byte[] encoded) throws FormatException {
try {
return crypto.getAgreementKeyParser().parsePublicKey(encoded);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
}
@Override
public PrivateKey parsePrivateKey(byte[] encoded) throws FormatException {
try {
return crypto.getAgreementKeyParser().parsePrivateKey(encoded);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
}
}

View File

@@ -0,0 +1,408 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfIncomingMessageHook;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginFactory;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.Collections.singletonMap;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.Bytes.compare;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.State.ACTIVATED;
import static org.briarproject.bramble.transport.agreement.State.AWAIT_ACTIVATE;
import static org.briarproject.bramble.transport.agreement.State.AWAIT_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
@Immutable
@NotNullByDefault
class TransportKeyAgreementManagerImpl extends BdfIncomingMessageHook
implements TransportKeyAgreementManager, OpenDatabaseHook, ContactHook,
ClientVersioningHook {
private static final Logger LOG =
getLogger(TransportKeyAgreementManagerImpl.class.getName());
private final ContactGroupFactory contactGroupFactory;
private final ClientVersioningManager clientVersioningManager;
private final IdentityManager identityManager;
private final KeyManager keyManager;
private final MessageEncoder messageEncoder;
private final SessionEncoder sessionEncoder;
private final SessionParser sessionParser;
private final TransportKeyAgreementCrypto crypto;
private final List<TransportId> transports;
private final Group localGroup;
@Inject
TransportKeyAgreementManagerImpl(
DatabaseComponent db,
ClientHelper clientHelper,
MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory,
ClientVersioningManager clientVersioningManager,
IdentityManager identityManager,
KeyManager keyManager,
MessageEncoder messageEncoder,
SessionEncoder sessionEncoder,
SessionParser sessionParser,
TransportKeyAgreementCrypto crypto,
PluginConfig config) {
super(db, clientHelper, metadataParser);
this.contactGroupFactory = contactGroupFactory;
this.clientVersioningManager = clientVersioningManager;
this.identityManager = identityManager;
this.keyManager = keyManager;
this.messageEncoder = messageEncoder;
this.sessionEncoder = sessionEncoder;
this.sessionParser = sessionParser;
this.crypto = crypto;
transports = new ArrayList<>();
for (PluginFactory<?> f : config.getDuplexFactories()) {
transports.add(f.getId());
}
for (PluginFactory<?> f : config.getSimplexFactories()) {
transports.add(f.getId());
}
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
MAJOR_VERSION);
}
@Override
public void onDatabaseOpened(Transaction txn) throws DbException {
Collection<Contact> contacts = db.getContacts(txn);
if (!db.containsGroup(txn, localGroup.getId())) {
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : contacts) addingContact(txn, c);
}
// Find any contacts and transports that need keys
Map<ContactId, Collection<TransportId>> transportsWithKeys =
db.getTransportsWithKeys(txn);
for (Contact c : contacts) {
Collection<TransportId> withKeys =
transportsWithKeys.get(c.getId());
for (TransportId t : transports) {
if (withKeys == null || !withKeys.contains(t)) {
// We need keys for this contact and transport
GroupId contactGroupId = getContactGroup(c).getId();
SavedSession ss = loadSession(txn, contactGroupId, t);
if (ss == null) {
// Start a session by sending our key message
startSession(txn, contactGroupId, t);
}
}
}
}
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group to share with the contact
Group g = getContactGroup(c);
db.addGroup(txn, g);
// Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId());
// Apply the client's visibility to the contact group
Visibility client = clientVersioningManager.getClientVisibility(txn,
c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c));
}
@Override
public void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException {
// Apply the client's visibility to the contact group
Group g = getContactGroup(c);
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
}
@Override
protected DeliveryAction incomingMessage(Transaction txn, Message m,
BdfList body, BdfDictionary meta)
throws DbException, FormatException {
MessageType type = MessageType.fromValue(
meta.getLong(MSG_KEY_MESSAGE_TYPE).intValue());
TransportId t = new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
if (LOG.isLoggable(INFO)) {
LOG.info("Received " + type + " message for " + t);
}
if (!transports.contains(t)) {
// Defer handling the message until we support the transport
return DEFER;
}
SavedSession ss = loadSession(txn, m.getGroupId(), t);
if (type == KEY) return handleKeyMessage(txn, t, m, meta, ss);
else if (type == ACTIVATE) return handleActivateMessage(txn, t, ss);
else throw new AssertionError();
}
private DeliveryAction handleKeyMessage(Transaction txn, TransportId t,
Message m, BdfDictionary meta, @Nullable SavedSession ss)
throws DbException, FormatException {
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
boolean haveKeys = db.containsTransportKeys(txn, c, t);
if (ss == null) {
if (haveKeys) {
// We have keys but no session, so we must have derived keys
// when adding the contact. If the contact didn't support
// the transport when they added us, they wouldn't have
// derived keys at that time. If they later added support for
// the transport then they would have started a session, so a
// key message is valid in this case
return handleKeyMessageForNewSession(txn, c, t, m, meta);
} else {
// We don't have keys, so we should have created a session at
// startup
throw new IllegalStateException();
}
} else if (ss.session.getState() == AWAIT_KEY) {
if (haveKeys) {
// We have keys, so we shouldn't be in the AWAIT_KEY state,
// even if the contact didn't derive keys when adding us and
// later started a session
throw new IllegalStateException();
} else {
// This is the key message we're waiting for
return handleKeyMessageForExistingSession(txn, c, t, m, meta,
ss);
}
} else {
return REJECT; // Not valid in this state
}
}
private DeliveryAction handleActivateMessage(Transaction txn,
TransportId t, @Nullable SavedSession ss) throws DbException {
if (ss != null && ss.session.getState() == AWAIT_ACTIVATE) {
// Activate the keys and finish the session
KeySetId keySetId = requireNonNull(ss.session.getKeySetId());
keyManager.activateKeys(txn, singletonMap(t, keySetId));
Session session = new Session(ACTIVATED,
ss.session.getLastLocalMessageId(), null, null, null);
saveSession(txn, t, ss.storageId, session);
return ACCEPT_DO_NOT_SHARE;
} else {
return REJECT; // Not valid in this state
}
}
private DeliveryAction handleKeyMessageForNewSession(Transaction txn,
ContactId c, TransportId t, Message m, BdfDictionary meta)
throws DbException, FormatException {
KeyPair localKeyPair = crypto.generateKeyPair();
PublicKey remotePublicKey =
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
Message keyMessage = sendKeyMessage(txn, m.getGroupId(), t,
localKeyPair.getPublic());
long minTimestamp = min(keyMessage.getTimestamp(), m.getTimestamp());
SecretKey rootKey;
try {
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
} catch (GeneralSecurityException e) {
return REJECT; // Invalid public key
}
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
minTimestamp, alice, false);
Message activateMessage =
sendActivateMessage(txn, m.getGroupId(), t, keyMessage.getId());
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
null, null, keySetId);
saveNewSession(txn, m.getGroupId(), t, session);
return ACCEPT_DO_NOT_SHARE;
}
private DeliveryAction handleKeyMessageForExistingSession(Transaction txn,
ContactId c, TransportId t, Message m, BdfDictionary meta,
SavedSession ss) throws DbException, FormatException {
KeyPair localKeyPair = requireNonNull(ss.session.getLocalKeyPair());
PublicKey remotePublicKey =
crypto.parsePublicKey(meta.getRaw(MSG_KEY_PUBLIC_KEY));
long localTimestamp = requireNonNull(ss.session.getLocalTimestamp());
long minTimestamp = min(localTimestamp, m.getTimestamp());
SecretKey rootKey;
try {
rootKey = crypto.deriveRootKey(localKeyPair, remotePublicKey);
} catch (GeneralSecurityException e) {
return REJECT; // Invalid public key
}
boolean alice = isLocalPartyAlice(txn, db.getContact(txn, c));
KeySetId keySetId = keyManager.addRotationKeys(txn, c, t, rootKey,
minTimestamp, alice, false);
MessageId previousMessageId =
requireNonNull(ss.session.getLastLocalMessageId());
Message activateMessage =
sendActivateMessage(txn, m.getGroupId(), t, previousMessageId);
Session session = new Session(AWAIT_ACTIVATE, activateMessage.getId(),
null, null, keySetId);
saveSession(txn, t, ss.storageId, session);
return ACCEPT_DO_NOT_SHARE;
}
private void startSession(Transaction txn, GroupId contactGroupId,
TransportId t) throws DbException {
KeyPair localKeyPair = crypto.generateKeyPair();
Message keyMessage = sendKeyMessage(txn, contactGroupId, t,
localKeyPair.getPublic());
Session session = new Session(AWAIT_KEY, keyMessage.getId(),
localKeyPair, keyMessage.getTimestamp(), null);
saveNewSession(txn, contactGroupId, t, session);
}
@Nullable
private SavedSession loadSession(Transaction txn, GroupId contactGroupId,
TransportId t) throws DbException {
try {
BdfDictionary query = sessionEncoder.getSessionQuery(t);
Collection<MessageId> ids =
clientHelper.getMessageIds(txn, contactGroupId, query);
if (ids.size() > 1) throw new DbException();
if (ids.isEmpty()) {
if (LOG.isLoggable(INFO)) LOG.info("No session for " + t);
return null;
}
MessageId storageId = ids.iterator().next();
BdfDictionary bdfSession =
clientHelper.getMessageMetadataAsDictionary(txn, storageId);
Session session = sessionParser.parseSession(bdfSession);
if (LOG.isLoggable(INFO)) {
LOG.info("Loaded session in state " + session.getState()
+ " for " + t);
}
return new SavedSession(session, storageId);
} catch (FormatException e) {
throw new DbException(e);
}
}
private void saveNewSession(Transaction txn, GroupId contactGroupId,
TransportId t, Session session) throws DbException {
Message m =
clientHelper.createMessageForStoringMetadata(contactGroupId);
db.addLocalMessage(txn, m, new Metadata(), false, false);
MessageId storageId = m.getId();
saveSession(txn, t, storageId, session);
}
private void saveSession(Transaction txn, TransportId t,
MessageId storageId, Session session) throws DbException {
if (LOG.isLoggable(INFO)) {
LOG.info("Saving session in state " + session.getState()
+ " for " + t);
}
BdfDictionary meta = sessionEncoder.encodeSession(session, t);
try {
clientHelper.mergeMessageMetadata(txn, storageId, meta);
} catch (FormatException e) {
throw new AssertionError();
}
}
private Message sendKeyMessage(Transaction txn, GroupId contactGroupId,
TransportId t, PublicKey publicKey) throws DbException {
Message m = messageEncoder.encodeKeyMessage(contactGroupId, t,
publicKey);
sendMessage(txn, t, m, KEY);
return m;
}
private Message sendActivateMessage(Transaction txn,
GroupId contactGroupId, TransportId t, MessageId previousMessageId)
throws DbException {
Message m = messageEncoder.encodeActivateMessage(contactGroupId, t,
previousMessageId);
sendMessage(txn, t, m, ACTIVATE);
return m;
}
private void sendMessage(Transaction txn, TransportId t, Message m,
MessageType type) throws DbException {
BdfDictionary meta =
messageEncoder.encodeMessageMetadata(t, type, true);
try {
clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) {
throw new AssertionError();
}
}
private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID,
MAJOR_VERSION, c);
}
private boolean isLocalPartyAlice(Transaction txn, Contact c)
throws DbException {
Author local = identityManager.getLocalAuthor(txn);
Author remote = c.getAuthor();
return compare(local.getId().getBytes(), remote.getId().getBytes()) < 0;
}
private static class SavedSession {
private final Session session;
private final MessageId storageId;
private SavedSession(Session session, MessageId storageId) {
this.session = session;
this.storageId = storageId;
}
}
}

View File

@@ -0,0 +1,83 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.CLIENT_ID;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.transport.agreement.TransportKeyAgreementManager.MINOR_VERSION;
@Module
public class TransportKeyAgreementModule {
public static class EagerSingletons {
@Inject
TransportKeyAgreementManager transportKeyAgreementManager;
@Inject
TransportKeyAgreementValidator transportKeyAgreementValidator;
}
@Provides
@Singleton
TransportKeyAgreementManager provideTransportKeyAgreementManager(
LifecycleManager lifecycleManager,
ValidationManager validationManager,
ContactManager contactManager,
ClientVersioningManager clientVersioningManager,
TransportKeyAgreementManagerImpl transportKeyAgreementManager) {
lifecycleManager.registerOpenDatabaseHook(transportKeyAgreementManager);
validationManager.registerIncomingMessageHook(CLIENT_ID,
MAJOR_VERSION, transportKeyAgreementManager);
contactManager.registerContactHook(transportKeyAgreementManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, transportKeyAgreementManager);
return transportKeyAgreementManager;
}
@Provides
@Singleton
TransportKeyAgreementValidator provideTransportKeyAgreementValidator(
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock, MessageEncoder messageEncoder,
ValidationManager validationManager) {
TransportKeyAgreementValidator validator =
new TransportKeyAgreementValidator(clientHelper,
metadataEncoder, clock, messageEncoder);
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
validator);
return validator;
}
@Provides
MessageEncoder provideMessageEncoder(MessageEncoderImpl messageEncoder) {
return messageEncoder;
}
@Provides
SessionEncoder provideSessionEncoder(SessionEncoderImpl sessionEncoder) {
return sessionEncoder;
}
@Provides
SessionParser provideSessionParser(SessionParserImpl sessionParser) {
return sessionParser;
}
@Provides
TransportKeyAgreementCrypto provideTransportKeyAgreementCrypto(
TransportKeyAgreementCryptoImpl transportKeyAgreementCrypto) {
return transportKeyAgreementCrypto;
}
}

View File

@@ -0,0 +1,79 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.api.system.Clock.MIN_REASONABLE_TIME_MS;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_PUBLIC_KEY;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable
@NotNullByDefault
class TransportKeyAgreementValidator extends BdfMessageValidator {
private final MessageEncoder messageEncoder;
TransportKeyAgreementValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock,
MessageEncoder messageEncoder) {
super(clientHelper, metadataEncoder, clock);
this.messageEncoder = messageEncoder;
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
MessageType type = MessageType.fromValue(body.getLong(0).intValue());
if (type == KEY) return validateKeyMessage(m.getTimestamp(), body);
else if (type == ACTIVATE) return validateActivateMessage(body);
else throw new AssertionError();
}
private BdfMessageContext validateKeyMessage(long timestamp, BdfList body)
throws FormatException {
if (timestamp < MIN_REASONABLE_TIME_MS) throw new FormatException();
// Message type, transport ID, public key
checkSize(body, 3);
String transportId = body.getString(1);
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
byte[] publicKey = body.getRaw(2);
checkLength(publicKey, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
new TransportId(transportId), KEY, false);
meta.put(MSG_KEY_PUBLIC_KEY, publicKey);
return new BdfMessageContext(meta);
}
private BdfMessageContext validateActivateMessage(BdfList body)
throws FormatException {
// Message type, transport ID, previous message ID
checkSize(body, 3);
String transportId = body.getString(1);
checkLength(transportId, 1, MAX_TRANSPORT_ID_LENGTH);
byte[] previousMessageId = body.getRaw(2);
checkLength(previousMessageId, MessageId.LENGTH);
BdfDictionary meta = messageEncoder.encodeMessageMetadata(
new TransportId(transportId), ACTIVATE, false);
MessageId dependency = new MessageId(previousMessageId);
return new BdfMessageContext(meta, singletonList(dependency));
}
}

View File

@@ -50,6 +50,7 @@ import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -173,8 +174,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
} }
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public DeliveryAction incomingMessage(Transaction txn, Message m,
throws DbException, InvalidMessageException { Metadata meta) throws DbException, InvalidMessageException {
try { try {
// Parse the new remote update // Parse the new remote update
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m)); Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
@@ -187,7 +188,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
&& latest.remote.updateVersion > newRemoteUpdateVersion) { && latest.remote.updateVersion > newRemoteUpdateVersion) {
db.deleteMessage(txn, m.getId()); db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId());
return false; return ACCEPT_DO_NOT_SHARE;
} }
// Load and parse the latest local update // Load and parse the latest local update
if (latest.local == null) throw new DbException(); if (latest.local == null) throw new DbException();
@@ -241,7 +242,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
return false; return ACCEPT_DO_NOT_SHARE;
} }
private void storeClientVersions(Transaction txn, private void storeClientVersions(Transaction txn,

View File

@@ -1,6 +1,3 @@
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0 Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0 Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0 Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0

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