Compare commits

..

230 Commits

Author SHA1 Message Date
akwizgran
6e61827fe6 Bump version numbers for 1.4.8 release. 2022-05-31 15:58:44 +01:00
akwizgran
2be93f6a49 Update translations. 2022-05-31 15:44:42 +01:00
Torsten Grote
5eb994d3e8 Merge branch 'update-tor-bridges' into 'master'
Update tor bridges

See merge request briar/briar!1653
2022-05-31 13:53:28 +00:00
akwizgran
f0c9819332 Update tor bridges 2022-05-31 13:53:27 +00:00
Torsten Grote
971dbf5df2 Merge branch '2229-mailbox-client' into 'master'
Add connectivity check tasks, refactor mailbox properties

See merge request briar/briar!1650
2022-05-31 12:45:27 +00:00
Torsten Grote
43a83df342 Merge branch '1499-tor-not-running' into 'master'
Crash as soon as we notice that Tor has stopped running

See merge request briar/briar!1652
2022-05-31 11:14:06 +00:00
akwizgran
0092f38bab Fix missing space in log message. 2022-05-31 11:46:09 +01:00
akwizgran
285a5f2928 Rethrow TorNotRunningException to get a crash report. 2022-05-31 11:46:09 +01:00
akwizgran
804049209d Upgrade jtorctl to 0.4. 2022-05-31 11:46:09 +01:00
akwizgran
2b1aed6caa Update test expectations. 2022-05-27 17:38:06 +01:00
akwizgran
44b0955b9d Allow status endpoint to be called with contact properties. 2022-05-27 17:26:55 +01:00
akwizgran
d43ef463a6 Check status endpoint with contact's auth token in integration test. 2022-05-27 13:01:26 +01:00
akwizgran
34337486e9 Use status endpoint for contact connectivity check. 2022-05-27 13:00:43 +01:00
akwizgran
3ebbb2a8cf Merge branch '2175-mailbox-problem-notification' into 'master'
Show notification warning when own mailbox is unreachable

Closes #2175

See merge request briar/briar!1648
2022-05-26 20:10:13 +00:00
Torsten Grote
54339afab8 Factor mailbox problem detection into MailboxStatus and constants 2022-05-26 14:07:06 -03:00
Torsten Grote
6c19b22aab Show notification warning when own mailbox is unreachable 2022-05-26 14:07:06 -03:00
akwizgran
6b790b59fa Use status endpoint for connectivity check. 2022-05-26 15:11:05 +01:00
akwizgran
8b61a0279b Move nested try block into private method. 2022-05-26 14:54:44 +01:00
akwizgran
94ce6bbb2c Reject mailbox owner properties for mailbox update. 2022-05-26 14:49:33 +01:00
akwizgran
845d505d2b Merge branch '2320-mailbox-metadata-format-exception' into 'master'
Bump major version of mailbox update client to avoid startup failure

Closes #2320

See merge request briar/briar!1647
2022-05-26 12:45:55 +00:00
akwizgran
6358518f88 Add connectivity checkers for our own mailbox and a contact's mailbox. 2022-05-26 13:40:31 +01:00
akwizgran
ef6e3bb2a7 Refactor MailboxProperties and MailboxUpdateWithMailbox. 2022-05-26 13:40:31 +01:00
akwizgran
8ec998f645 Replace Supplier<Boolean> with more legible ApiCall interface. 2022-05-26 13:40:31 +01:00
akwizgran
f75d63fc46 Merge branch '2234-mailbox-api-task' into 'master'
Abstract task for calling an API endpoint

Closes #2234

See merge request briar/briar!1649
2022-05-26 12:27:14 +00:00
akwizgran
0c22c25995 Submit first try to IoExecutor directly. 2022-05-25 12:45:23 +01:00
akwizgran
7e249ecf70 Add convenience class for simple API calls. 2022-05-24 15:10:07 +01:00
akwizgran
274963d9d1 Add MailboxApiCaller for calling API endpoints with retries. 2022-05-24 15:10:07 +01:00
akwizgran
18b3865a86 Factor out Cancellable interface from TaskScheduler. 2022-05-24 15:10:07 +01:00
akwizgran
f08688708a Bump client major version to ensure group metadata gets created. 2022-05-23 16:12:23 +01:00
akwizgran
c37f6069c7 Use feature flag to enable mailbox sync client. 2022-05-23 16:11:27 +01:00
Torsten Grote
c8caae49f1 Broadcast MailboxProblemEvent in case of mailbox problems
Also move other mailbox related events into the events package
2022-05-23 12:00:42 -03:00
Torsten Grote
670cc34b12 Merge branch 'tor-state-enabling-when-zero-onion-router-connections' into 'master'
Fix OR connection counts, set Tor status to ENABLING when not connected to any ORs

See merge request briar/briar!1646
2022-05-20 16:54:34 +00:00
akwizgran
f387c3801b Don't count pending OR connections, don't reset connection count.
Tor doesn't report status changes for bridge connections that fail during handshaking, which causes the pending connection count to become inaccurate.

We were resetting the connection counts when switching guard context, but this was a mistake caused by the pending connection count being inaccurate. The counts should not be reset, as Tor continues to report status changes for connected connections belonging to the old context.

It's no longer necessary to disable and re-enable the network when the Tor settings are updated. This only appeared to be necessary because we were wrongly resetting the connection counts.
2022-05-20 17:20:34 +01:00
Torsten Grote
aa759a636e Merge branch 'limit-connection-pool-size' into 'master'
Keep one connection in the DB pool

See merge request briar/briar!1644
2022-05-20 14:23:47 +00:00
akwizgran
0b85aca932 Remove connectivity workaround that should no longer be needed. 2022-05-20 14:48:00 +01:00
akwizgran
d4cdedeed7 Set status to ENABLING when not connected to any guards/bridges. 2022-05-20 14:48:00 +01:00
Torsten Grote
9b10c12f23 Merge branch 'refactor-tor-plugin-factories' into 'master'
Refactor Tor plugin factories

See merge request briar/briar!1645
2022-05-20 11:55:23 +00:00
akwizgran
2bf490b973 Use non-default Tor ports for BridgeTest. 2022-05-19 14:12:36 +01:00
akwizgran
d2f25f2ebe Refactor Tor plugin factories. 2022-05-19 14:12:36 +01:00
akwizgran
b3dcde9187 Merge branch '2301-update-contacts-about-change-in-mailbox-versions-that-client-supports' into 'master'
Send mailbox update to contacts if supported mailbox versions changed

Closes #2301

See merge request briar/briar!1642
2022-05-19 12:17:39 +00:00
Daniel Lublin
241e5e9f6e Test for update sent when clientSupports is found to have changed on startup 2022-05-19 09:35:46 +02:00
Daniel Lublin
c59524df65 Make current tests independent of real CLIENT_SUPPORTS 2022-05-19 09:06:40 +02:00
Daniel Lublin
4467f9e260 Keep last sent clientSupports on record, sending update only if changed 2022-05-19 09:06:40 +02:00
Daniel Lublin
7e215e7f84 Keep MailboxUpdate immutable 2022-05-19 09:06:40 +02:00
Daniel Lublin
601ff50294 Send mailbox update to contacts if supported mailbox versions changed 2022-05-19 09:06:40 +02:00
akwizgran
9f839d9d12 Merge branch '2309-troubleshooting-wizard' into 'master'
Troubleshooting wizard for mailbox

Closes #2309

See merge request briar/briar!1640
2022-05-18 17:00:50 +00:00
akwizgran
1e4c28a30a Merge branch '2299-method-for-fetching-mailboxs-supported-api-versions' into 'master'
Add method for fetching api versions that the mailbox supports

Closes #2299

See merge request briar/briar!1643
2022-05-18 12:18:48 +00:00
Daniel Lublin
bc0f9a984c Add method for fetching api versions that the mailbox supports 2022-05-17 14:49:00 +02:00
Torsten Grote
15e0abffb0 Address review feedback for mailbox troubleshooting wizard 2022-05-16 11:44:23 -03:00
Torsten Grote
5254efb630 Troubleshooting wizard for mailbox 2022-05-16 10:56:58 -03:00
Torsten Grote
df22df22a0 Merge branch '2277-activity-not-found-exception' into 'master'
Catch ActivityNotFoundException when choosing files

Closes #2277

See merge request briar/briar!1628
2022-05-16 12:52:23 +00:00
Torsten Grote
23681ff7f7 Merge branch 'emoji-0.7.0' into 'master'
Upgrade emoji library to 0.7.0

Closes #1775

See merge request briar/briar!1630
2022-05-16 12:35:23 +00:00
Torsten Grote
57bebc0b87 Merge branch '2261-extra-logging' into 'master'
Add logging for message delivery, comments for initial messages exchanged in tests

See merge request briar/briar!1641
2022-05-16 11:18:02 +00:00
akwizgran
82057da962 Sync acks when re-adding contacts. 2022-05-16 11:05:14 +01:00
akwizgran
00b7518e49 Add comments to explain initial messages exchanged with contacts. 2022-05-16 11:05:10 +01:00
akwizgran
418ab99a3c Log client IDs when validating and delivering messages. 2022-05-16 11:04:25 +01:00
akwizgran
49c14af0dc Merge branch '2261-include-mailbox-api-version-in-mailbox-properties' into 'master'
Include mailbox API version in local and remote mailbox properties

Closes #2261

See merge request briar/briar!1621
2022-05-16 09:58:30 +00:00
Daniel Lublin
3f7aed7886 Rename to Mailbox update 2022-05-16 10:21:41 +02:00
Daniel Lublin
d2728dd29b Assert that we have a local update 2022-05-16 10:07:35 +02:00
Daniel Lublin
84afc6d934 Let integration tests mind the mailbox prop update when adding contact 2022-05-13 16:19:51 +02:00
Daniel Lublin
a42d9eec1c Include mailbox API version in local and remote mailbox properties
This changes the format of the mailbox properties update message, so
the major version of the client is bumped.
2022-05-13 16:19:51 +02:00
akwizgran
5d5d8d206c Merge branch '2298-fetch-and-store-mailboxs-supported-api-versions-when-pairing' into 'master'
Fetch and store mailbox's supported API versions when pairing mailbox

Closes #2298

See merge request briar/briar!1622
2022-05-12 14:12:40 +00:00
akwizgran
5237df32e3 Merge branch '2226-defer-marking-messages-and-acks-as-sent' into 'master'
Defer marking messages and acks as sent

Closes #2296

See merge request briar/briar!1635
2022-05-12 13:59:17 +00:00
akwizgran
72e376f152 Merge branch 'db-shutdown-race' into 'master'
Fix race condition in DB shutdown

See merge request briar/briar!1620
2022-05-12 13:57:45 +00:00
Daniel Lublin
4d685a2617 Fetch and store mailbox's supported api versions when pairing 2022-05-09 14:17:30 +02:00
Daniel Lublin
16ab48d009 Allow storing int array in settings 2022-05-01 08:23:27 +02:00
akwizgran
095bebf524 Merge branch 'use-do-not-kill-lib' into 'master'
Use do-not-kill-me-lib

See merge request briar/briar!1636
2022-04-29 14:49:42 +00:00
Torsten Grote
b67d9935c7 Update do-not-kill-lib to 0.2.2 2022-04-29 07:49:06 -03:00
akwizgran
34aea945cb Merge branch '2311-remind-to-wipe' into 'master'
Remind user to wipe mailbox if it's unreachable when unpairing

Closes #2311

See merge request briar/briar!1639
2022-04-27 17:00:12 +00:00
akwizgran
a82666b8bd Merge branch '2173-unlink-mailbox' into 'master'
Implement UI for unpairing the mailbox

Closes #2173

See merge request briar/briar!1637
2022-04-27 16:47:46 +00:00
akwizgran
e614046662 Use longs to represent session capacity. 2022-04-27 17:45:11 +01:00
akwizgran
0691354952 Defer marking messages and acks as sent. 2022-04-27 17:45:11 +01:00
Torsten Grote
aa997a9c64 Tweak mailbox wipe reminder string 2022-04-27 13:41:58 -03:00
Torsten Grote
f05cbac20a Use a new WasUnpaired state that shows a dialog over a blank fragment 2022-04-27 12:14:26 -03:00
Torsten Grote
39c74f1363 Annotate MailboxApi with @NotNullByDefault 2022-04-27 11:57:39 -03:00
Torsten Grote
2411c82d9c Remind user to wipe mailbox if it's unreachable when unpairing
If we fail to tell the mailbox to wipe itself when unpairing, remind the user that they should wipe the mailbox next time they have access to it.
2022-04-27 10:44:27 -03:00
Torsten Grote
f43839dbb3 Upgrade translations for dont-kill-me lib 2022-04-27 10:40:03 -03:00
Torsten Grote
3138213f39 Let MailboxManager#unPair() return a boolean for whether it could wipe the mailbox 2022-04-27 08:38:43 -03:00
Torsten Grote
d080af4b7a Show a warning before unlinking the mailbox when there haven't been any connection failures 2022-04-27 08:07:12 -03:00
Torsten Grote
9d19761dbe Tweak mailbox unlink dialog warning 2022-04-27 07:56:30 -03:00
Torsten Grote
fa3a5be083 Implement UI for unpairing the mailbox 2022-04-26 15:18:29 -03:00
Torsten Grote
fa3db0f888 Add method for unpairing to MailboxManager 2022-04-26 15:18:29 -03:00
akwizgran
4b7ee62190 Merge branch 'invite-button-change' into 'master'
Moved invite buttons in groups and forums to overflow menu.

See merge request briar/briar!1638
2022-04-26 11:51:50 +00:00
FlyingP1g FlyingP1g
9d3c33fdbc Moved invite buttons in groups and forums to overflow menu. 2022-04-23 22:48:01 +03:00
Torsten Grote
37d4ca84f7 Adapt tests to do-not-kill lib as well 2022-04-20 13:56:37 -03:00
Torsten Grote
1b58d986ae Use dependency from maven central as it produces a different integrity assertion than maven local 2022-04-20 13:56:37 -03:00
Torsten Grote
784c7416ec Use do-not-kill-me-lib 2022-04-20 13:56:34 -03:00
akwizgran
7536f16c61 Bump version numbers for 1.4.7 release. 2022-04-20 16:35:07 +01:00
akwizgran
ab628c1921 Update translations. 2022-04-20 16:14:58 +01:00
akwizgran
85e53479f2 Merge branch '2172-mailbox-status-ui' into 'master'
Improve MailboxStatusFragment and record check failures as well

Closes #2172

See merge request briar/briar!1632
2022-04-20 15:04:31 +00:00
akwizgran
116ee97056 Merge branch '1980-catch-security-exceptions-from-connectivity-manager' into 'master'
Catch SecurityExceptions from all ConnectivityManager calls

Closes #1980

See merge request briar/briar!1634
2022-04-20 14:51:57 +00:00
Torsten Grote
78938f1ac6 Merge branch '2280-check-lifecycle-before-recreating-removable-drive-tasks' into 'master'
Check lifecycle state before recreating removable drive tasks

Closes #2280

See merge request briar/briar!1629
2022-04-20 13:37:24 +00:00
akwizgran
afff66eaff Don't assume that non-null WifiInfo means we're connected to wifi. 2022-04-20 12:42:35 +01:00
akwizgran
8c33ea5a6b Add javadocs for database. 2022-04-19 13:04:35 +01:00
akwizgran
96228c1fd0 Do all of SimplexOutgoingSession's work on the IoExecutor. 2022-04-19 13:04:35 +01:00
akwizgran
eb6a5fe63e Try GET_CONTENT first, fall back to OPEN_DOCUMENT. 2022-04-19 12:57:58 +01:00
akwizgran
a8624cd507 Guess connectivity when ConnectivityManager is broken. 2022-04-19 11:27:40 +01:00
akwizgran
e7fc37d81e Catch SecurityExceptions from all ConnectivityManager calls.
This issue occurs on Android 11 and no fix is expected. When the issue occurs, Tor connectivity and outgoing LAN connectivity will be broken until the app is restarted.
2022-04-19 11:03:08 +01:00
Torsten Grote
7bd220f18d Merge branch 'clear-glide-cache-under-more-circumstances' into 'master'
Clear the Glide cache in response to a wider range of warnings

See merge request briar/briar!1633
2022-04-18 16:46:31 +00:00
akwizgran
7f581fee15 Merge branch 'master' into '2277-activity-not-found-exception'
# Conflicts:
#   briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java
2022-04-18 16:18:14 +00:00
akwizgran
383056d37e Fix the lint problems I created for myself. 2022-04-18 15:57:18 +01:00
akwizgran
23316f5e9c Don't use OPEN_DOCUMENT launchers on API < 19. 2022-04-18 15:42:24 +01:00
Torsten Grote
dea05c85a2 Improve MailboxStatusFragment and record check failures as well 2022-04-18 10:16:38 -03:00
akwizgran
b36066514b Add SDK_INT check to appease the linter. 2022-04-18 14:02:25 +01:00
akwizgran
f9403782a2 When opening files, try OPEN_DOCUMENT and fall back to GET_CONTENT. 2022-04-18 14:02:03 +01:00
Torsten Grote
174ca3cfb8 Merge branch '2214-catch-activity-not-found-exception-when-saving-image' into 'master'
Catch ActivityNotFoundException when saving image

Closes #2214

See merge request briar/briar!1627
2022-04-18 12:44:20 +00:00
akwizgran
961af66c8e Use new onSaveImageError() method for readability. 2022-04-18 13:33:09 +01:00
Torsten Grote
a86ea454d0 Merge branch '2143-rethrow-security-exceptions-when-opening-images' into 'master'
Rethrow SecurityExceptions when opening images

Closes #2143

See merge request briar/briar!1626
2022-04-18 12:12:20 +00:00
Torsten Grote
a7877bf7ee Merge branch '2273-rethrow-security-exceptions-for-removable-drives' into 'master'
Rethrow SecurityExceptions when opening files on removable drives

Closes #2273

See merge request briar/briar!1625
2022-04-18 12:11:42 +00:00
Torsten Grote
62ae0f745b Merge branch '2306-task-scheduler-zero-delay' into 'master'
Fixe race condition in AndroidTaskScheduler

Closes #2306

See merge request briar/briar!1624
2022-04-18 11:57:34 +00:00
Torsten Grote
f83abbe63d Merge branch '2305-increase-tor-connection-timeout' into 'master'
Increase Tor connection timeout to 2 minutes

Closes #2305

See merge request briar/briar!1623
2022-04-18 11:53:57 +00:00
Torsten Grote
e0b6b8435d Merge branch 'update-introduction-onboarding-text' into 'master'
Update introduction onboarding text

See merge request briar/briar!1631
2022-04-18 11:49:41 +00:00
akwizgran
d3c7832245 Update introduction onboarding text.
The old text caused some confusion in user testing because contacts can now add each other remotely.
2022-04-18 11:34:22 +01:00
akwizgran
cc4978c2b1 Upgrade emoji library to 0.7.0. 2022-04-18 10:45:49 +01:00
akwizgran
a043e8b1cf Check lifecycle state before recreating removable drive tasks. 2022-04-17 12:28:26 +01:00
akwizgran
97ba18cfb2 Catch ActivityNotFoundException when choosing files. 2022-04-17 12:12:02 +01:00
akwizgran
bc013296f6 Catch ActivityNotFoundException when saving image. 2022-04-17 11:59:00 +01:00
akwizgran
c1fabcd46b Rethrow SecurityExceptions when opening images. 2022-04-17 11:51:49 +01:00
akwizgran
3c08e86822 Rethrow SecurityExceptions when opening files on removable drives. 2022-04-17 11:36:16 +01:00
akwizgran
de2c9670d5 Clear the Glide cache in response to a wider range of warnings. 2022-04-17 10:50:59 +01:00
akwizgran
9632754274 Ensure task is added to queue before queue is checked. 2022-04-16 19:32:51 +01:00
akwizgran
b275a0ffff Increase Tor connection timeout to 2 minutes. 2022-04-16 16:07:03 +01:00
akwizgran
74a3f54d28 Merge branch '2172-mailbox-status-ui' into 'master'
Implement status UI for mailbox connection

Closes #2172

See merge request briar/briar!1617
2022-04-14 12:46:28 +00:00
Torsten Grote
edcb234b93 Show OfflineFragment when TorPlugin becomes inactive in mailbox flow 2022-04-12 10:10:09 -03:00
Torsten Grote
dae00c7e4e Show different mailbox status in UI
and show failure status after unsuccessful attempt
2022-04-12 10:01:43 -03:00
Torsten Grote
29b16c4d74 Re-use OfflineFragment when offline in mailbox status screen 2022-04-12 09:35:39 -03:00
akwizgran
edd270abf3 Keep one connection in the DB pool.
For H2, this ensures we're not constantly closing and reopening the DB.
2022-04-08 16:06:35 +01:00
akwizgran
47d412dd0a Limit the size of the DB connection pool. 2022-04-08 15:50:49 +01:00
akwizgran
5d952ff68e Don't return connections to the pool if they've thrown exceptions. 2022-04-08 15:49:43 +01:00
akwizgran
9304a6b266 Continue with closing connections if an exception is thrown. 2022-04-08 15:37:02 +01:00
akwizgran
a99ec5ed51 Fix a race condition when starting a transaction during shutdown. 2022-04-08 15:28:13 +01:00
Torsten Grote
40d58a9359 Prevent memory leak and crash when refreshing MailboxStatusFragment 2022-04-07 11:00:41 -03:00
Torsten Grote
60a1a4d2d1 Make MailboxManager#checkConnection() blocking and let the UI manage the executor 2022-04-07 10:44:24 -03:00
Torsten Grote
238aeb3abd Merge branch 'extend-timeout-for-pre-release-tests' into 'master'
Extend timeout for pre-release tests

See merge request briar/briar!1618
2022-04-04 11:13:50 +00:00
akwizgran
62c16fad09 Merge branch '2191-reset-retransmission-times-when-contacts-mailbox-props-change' into 'master'
Reset retransmission times when contact's mailbox props change

Closes #2191

See merge request briar/briar!1619
2022-04-04 10:19:02 +00:00
Daniel Lublin
68e57bda0d Reset retransmission times when contact's mailbox props change 2022-04-04 12:01:19 +02:00
akwizgran
0df73dbf0a Extend timeout for pre-release tests. 2022-04-02 08:16:34 +01:00
Torsten Grote
5b648cbd35 Add connection check button to Mailbox status UI
and update the last connection timestamp accordingly
2022-04-01 13:55:11 -03:00
Torsten Grote
5e7891d78a Add checkConnection() to MailboxManager 2022-04-01 13:55:11 -03:00
akwizgran
d5e17c8201 Bump version numbers for 1.4.6 release. 2022-04-01 17:05:12 +01:00
Torsten Grote
d572ae71e7 Merge branch 'more-non-default-bridges' into 'master'
Vanilla bridges

See merge request briar/briar!1611
2022-04-01 16:02:58 +00:00
akwizgran
2e9d9dac84 Update translations. 2022-04-01 16:45:59 +01:00
akwizgran
573817c4c9 Map el to el-GR for Play Store metadata. 2022-04-01 16:44:07 +01:00
Torsten Grote
4f00f39d3f Merge branch 'initialise-mailbox-eager-singletons' into 'master'
Initialise mailbox eager singletons at startup

See merge request briar/briar!1616
2022-04-01 15:30:47 +00:00
akwizgran
c7d3628ecb Update Play Store metadata. 2022-04-01 16:22:46 +01:00
akwizgran
b198bef5f8 Initialise mailbox eager singletons at startup. 2022-04-01 16:02:12 +01:00
Torsten Grote
cff94009a1 Merge branch 'tor-0.4.5' into 'master'
Upgrade to Tor 0.4.5.12-1 and obfs4proxy 0.0.12

See merge request briar/briar!1608
2022-04-01 14:00:50 +00:00
Torsten Grote
44f9f0bbc5 Merge branch 'more-tor-events' into 'master'
Log more Tor events and react to CIRCUIT_NOT_ESTABLISHED

See merge request briar/briar!1605
2022-04-01 13:22:09 +00:00
akwizgran
5fdb43ce9b Merge branch '2192-reset-retransmission-times-when-pairing-mailbox' into 'master'
Reset retransmission times when pairing (new) mailbox

Closes #2192

See merge request briar/briar!1615
2022-04-01 13:08:47 +00:00
Daniel Lublin
725d11d960 Extend test 2022-04-01 14:56:05 +02:00
Daniel Lublin
7cf2c2faa7 Reset retransmission times when pairing (new) mailbox 2022-04-01 14:56:05 +02:00
akwizgran
4b3c26feb6 Merge branch 'fix_openOutputStream' into 'master'
Fix usage of ContentResolver.openOutputStream()

See merge request briar/briar!1607
2022-04-01 12:36:50 +00:00
akwizgran
2fbeb29195 Merge branch 'simplify-and-get-rid-of-contactmanager' into 'master'
Simplify and get rid of injected contactmanager

See merge request briar/briar!1614
2022-04-01 11:18:43 +00:00
akwizgran
5892fba237 Merge branch '2267-broadcast-event-when-recording-own-mailbox-connection-status' into 'master'
Broadcast event when recording connection status of own mailbox

Closes #2267

See merge request briar/briar!1613
2022-04-01 11:17:47 +00:00
akwizgran
cc9f04980a Merge branch 'fix-exception-logging' into 'master'
Don't warn about background exceptions unless one was thrown

See merge request briar/briar!1610
2022-04-01 11:15:33 +00:00
Daniel Lublin
44fb2a5c59 Use db directly, get rid of injected ContactManager 2022-04-01 10:31:52 +02:00
Daniel Lublin
68e534348f Broadcast event when recording connection status of own mailbox 2022-04-01 09:14:50 +02:00
akwizgran
795a8f1e70 Upgrade Tor to 0.4.5.12-2. 2022-03-31 14:20:17 +01:00
akwizgran
bf968b227e Merge branch '2295-broadcast-event-on-contact-mailbox-props-update' into 'master'
Broadcast event when a contact's Mailbox properties are updated

Closes #2295

See merge request briar/briar!1612
2022-03-31 09:24:00 +00:00
Daniel Lublin
8b94dad01f Broadcast event when a contact's Mailbox properties are updated 2022-03-31 09:20:19 +02:00
akwizgran
fa0610fff1 Use vanilla bridges in parallel with obfs4 bridges. 2022-03-30 18:01:28 +01:00
Torsten Grote
1d94db8d60 Merge branch 'compact-db-at-shutdown' into 'master'
Always compact the DB at shutdown

See merge request briar/briar!1609
2022-03-30 16:29:29 +00:00
akwizgran
1d4f450960 Update translations. 2022-03-30 17:03:29 +01:00
akwizgran
7f6b31d36c Don't warn about background exceptions unless one was thrown. 2022-03-30 16:54:11 +01:00
akwizgran
05737d858d Increase number of unreachable bridges allowed. 2022-03-30 14:30:44 +01:00
akwizgran
2c8e2ab6b8 Remove two unreliable non-default bridges. 2022-03-30 14:29:55 +01:00
akwizgran
97f64fb31c Remove the max compaction interval.
We always compact on shutdown, or on startup if we've shut down uncleanly without compacting.
2022-03-30 13:32:45 +01:00
akwizgran
e66152e812 Compact the DB at shutdown, and at startup if not closed cleanly. 2022-03-30 13:32:39 +01:00
akwizgran
101ffa2f08 Merge branch '2181-implement-sync-client-for-mailbox-props' into 'master'
Implement sync client for mailbox properties

Closes #2181

See merge request briar/briar!1591
2022-03-30 12:29:15 +00:00
Daniel Lublin
13eebe393a Rename, clarifying this is not an address; it has no scheme, no .onion 2022-03-30 13:32:28 +02:00
Daniel Lublin
5bc5791ddb Implement the Mailbox property client 2022-03-30 13:32:28 +02:00
akwizgran
a35e9af1de Add method for generating a unique ID, remove equals() methods. 2022-03-30 13:23:59 +02:00
akwizgran
ade89c14c4 Update translations. 2022-03-30 11:28:58 +01:00
akwizgran
16cfb89310 Add more non-default bridges. 2022-03-29 16:47:33 +01:00
akwizgran
78f00863dd Upgrade to obfs4proxy 0.0.12. 2022-03-29 16:39:07 +01:00
akwizgran
bd50a109cd Keep empty directory for unpacking Tor binaries. 2022-03-29 16:19:47 +01:00
akwizgran
38c91aea32 Upgrade Tor to 0.4.5.12-1. 2022-03-29 15:49:40 +01:00
akwizgran
92517ae7c0 Merge branch '2265-replace-eta-with-maxlatency-retransmission-logic' into 'master'
Replace ETA with max latency in retransmission logic

Closes #2265

See merge request briar/briar!1606
2022-03-29 13:12:38 +00:00
Daniel Lublin
dd1c8c8301 Replace ETA with max latency in retransmission logic 2022-03-29 14:57:03 +02:00
Torsten Grote
edc1029e92 Merge branch '2270-huawei-power-management-crash' into 'master'
Catch exception in Huawei power management setup

Closes #2270

See merge request briar/briar!1602
2022-03-29 12:54:16 +00:00
akwizgran
27e9338a12 Merge branch 'exceptions-on-thread-in-bramble-testcases' into 'master'
Exceptions on thread in bramble testcases

See merge request briar/briar!1584
2022-03-29 10:59:25 +00:00
Sebastian Kürten
243df3096a Add logging message when BrambleTestCase detects background thread exception 2022-03-29 09:30:51 +02:00
cketti
50f9718037 Truncate existing file when writing to removable drive 2022-03-29 01:27:01 +02:00
cketti
88c8bd32a5 Truncate existing file when saving image 2022-03-29 01:22:46 +02:00
akwizgran
3e597ceff8 Use a constructor that Animal Sniffer knows about. 2022-03-28 15:09:26 +01:00
akwizgran
3d6972fd73 Fix race condition in IntroductionIntegrationTest. 2022-03-28 14:59:43 +01:00
akwizgran
288f3331ec Include background exception in test failure report. 2022-03-28 14:59:01 +01:00
akwizgran
a14ee55f12 Update translations. 2022-03-21 14:19:02 +00:00
akwizgran
2a85907565 Update translations. 2022-03-21 14:16:34 +00:00
Torsten Grote
1fe7b2f451 Merge branch 'transifex-migration' into 'master'
Migrate to Transifex Go client

See merge request briar/briar!1603
2022-03-21 12:23:01 +00:00
akwizgran
585ceb626b Merge branch 'update-bridge-config' into 'master'
Update circumvention config

See merge request briar/briar!1604
2022-03-15 12:52:22 +00:00
akwizgran
5da782cf18 Turkmenistan needs non-default bridges. 2022-03-15 12:07:59 +00:00
akwizgran
ec6b999d30 Belarus needs non-default bridges. 2022-03-15 12:06:34 +00:00
akwizgran
d8a925a94f Remove Syria and Turkey from list of countries blocking Tor.
https://explorer.ooni.org/chart/circumvention?since=2021-12-13&until=2022-03-13&probe_cc=BY%2CCN%2CEG%2CIR%2CRU%2CSY%2CTR%2CVE
2022-03-15 12:06:34 +00:00
akwizgran
3de4386e63 Log the scrubbed onion address when an HS descriptor is uploaded.
This allows us to distinguish between descriptors for our permanent hidden service and rendezvous hidden services.
2022-03-15 12:04:22 +00:00
akwizgran
8c60787866 Fix inverted logic. 2022-03-15 12:04:22 +00:00
akwizgran
fa8ca8e6cf Log more Tor events and react to CIRCUIT_NOT_ESTABLISHED.
In future we should also react to CLOCK_SKEW and maybe CLOCK_JUMPED.
2022-03-15 12:04:22 +00:00
akwizgran
07814d43de Restore comment to Transifex config. 2022-03-15 11:57:30 +00:00
akwizgran
d80ba0f556 Migrate to Transifex Go client. 2022-03-15 10:35:35 +00:00
akwizgran
d70e1ed32e Merge branch 'make-bridge-test-more-robust' into 'master'
Make bridge test more robust

See merge request briar/briar!1601
2022-03-10 17:15:29 +00:00
akwizgran
eec2c87797 Catch exception in Huawei power management setup. 2022-03-10 12:40:32 +00:00
akwizgran
a256027916 Tolerate up to 4 unreachable bridges per run. 2022-03-10 10:03:06 +00:00
akwizgran
bf0f99277a Remove four consistently failing bridges. 2022-03-10 10:02:00 +00:00
akwizgran
2d62deb2db Increase CI timeout for BridgeTest. 2022-03-09 12:57:20 +00:00
akwizgran
e3682bb331 Make more attempts per bridge to try to get stable results. 2022-03-09 12:54:34 +00:00
akwizgran
6805040ac4 Make BridgeTest more robust by trying each bridge three times. 2022-03-08 22:45:39 +00:00
akwizgran
4198e1f22a Add some default bridges from Tor Browser. 2022-03-08 22:44:47 +00:00
akwizgran
ee11d2a28d Merge branch '2269-use-full-camera-preview-when-scanning-qr-codes' into 'master'
Use whole preview image when decoding QR

Closes #2269

See merge request briar/briar!1600
2022-03-08 15:02:25 +00:00
Daniel Lublin
f3718e496c Use whole preview image when decoding QR
When scanning Mailbox QR, the whole preview is visible on screen, so we
should use it. We choose to never crop the preview, because it was
originally a speed optimization which no longer is needed.
2022-03-08 13:32:57 +01:00
Torsten Grote
414c296abd Merge branch '2272-get-network-interfaces-npe' into 'master'
Catch NPE thrown by NetworkInterface.getNetworkInterfaces()

Closes #2272

See merge request briar/briar!1599
2022-03-07 11:39:44 +00:00
akwizgran
79051439c5 Catch NPE thrown by NetworkInterface.getNetworkInterfaces(). 2022-03-05 13:54:48 +00:00
Sebastian Kürten
32b62d3e30 Allow BrambleTestCase to handle background thread exceptions gracefully during after() 2022-03-04 18:00:16 +01:00
Sebastian Kürten
e3f2a30120 Make BrambleTestCase fail if background thread throws an exception 2022-03-04 18:00:15 +01:00
Sebastian Kürten
58a122ee28 Add test that checks exception handling on background threads 2022-03-04 18:00:09 +01:00
akwizgran
f5f7b3eb51 Merge branch 'master' into 'master'
Changed from share icon to plus icon.

See merge request briar/briar!1596
2022-03-02 12:05:58 +00:00
FlyingP1g FlyingP1g
098128c8a8 Changed from share icon to plus icon. 2022-03-02 12:05:57 +00:00
Torsten Grote
27d566df7a Merge branch 'memory-stats' into 'master'
Collect some more memory stats for help with debugging OOM errors

See merge request briar/briar!1595
2022-02-28 14:58:07 +00:00
akwizgran
9469825f4f Collect some more memory stats for help with debugging OOM errors. 2022-02-28 13:58:15 +00:00
Torsten Grote
5ce90422c6 Merge branch 'update-play-store-metadata' into 'master'
Update Play Store metadata

See merge request briar/briar!1593
2022-02-28 12:39:17 +00:00
akwizgran
256662e094 Merge branch 'fix-screenshot-tests' into 'master'
Fix screenshot PromoVideoTest

See merge request briar/briar!1594
2022-02-25 18:09:37 +00:00
Torsten Grote
dc7f1e0c86 Fix screenshot PromoVideoTest
Needs also to upgrade androidTestImplementation dependencies, but this makes normal instrumentation tests fail due to method limit.
So those are not committed.
2022-02-25 14:37:28 -03:00
akwizgran
a54e1d424c Merge branch '2162-mailbox-pairing-ui-end' into 'master'
Implement final parts of UI for pairing Briar with mailbox

Closes #2162

See merge request briar/briar!1590
2022-02-25 14:27:34 +00:00
Torsten Grote
9fa3ee18a4 Capitalize more words and fix duplicate string 2022-02-25 11:17:49 -03:00
akwizgran
84be347695 Update Play Store metadata. 2022-02-24 16:00:37 +00:00
Torsten Grote
952ac2c922 Simplify fragment transitions for mailbox pairing UI
Now, trying again always starts before scanning, so the user needs to scan the code again.
2022-02-22 14:43:30 -03:00
Torsten Grote
4390c810d1 Address first round of review feedback for mailbox pairing UI 2022-02-21 14:32:28 -03:00
Torsten Grote
a567301e49 Add a minimal MailboxStatusFragment 2022-02-18 12:57:32 -03:00
Torsten Grote
5e8d5c96fc Implement UI for mailbox pairing error and final states 2022-02-18 12:57:32 -03:00
Torsten Grote
80d804d280 Use new MailboxManager in Android UI 2022-02-18 12:57:32 -03:00
Torsten Grote
7fad299cf0 Add network_security_config so we are allowed to connect to onion addresses
Otherwise trying to connect without TLS will throw an exception.
2022-02-18 12:57:32 -03:00
282 changed files with 9881 additions and 2978 deletions

View File

@@ -98,6 +98,7 @@ bridge test:
allow_failure: true
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 3h
mailbox integration test:
extends: .optional_tests
@@ -122,5 +123,6 @@ pre_release_tests:
extends: .optional_tests
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
timeout: 3h
only:
- tags

View File

@@ -15,8 +15,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10405
versionName "1.4.5"
versionCode 10408
versionName "1.4.8"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -43,7 +43,7 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor "org.briarproject:tor-android:$tor_version"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version@zip"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -70,11 +70,6 @@ clean.dependsOn cleanTorBinaries
task unpackTorBinaries {
doLast {
copy {
from configurations.tor.collect { zipTree(it) }
into torBinariesDir
include 'geoip.zip'
}
configurations.tor.each { outer ->
zipTree(outer).each { inner ->
if (inner.name.endsWith('_arm_pie.zip')) {

View File

@@ -11,7 +11,10 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.lifecycle.Service;
@@ -21,7 +24,6 @@ import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import java.net.Inet4Address;
import java.net.InetAddress;
@@ -38,6 +40,7 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
@@ -111,15 +114,37 @@ class AndroidNetworkManager implements NetworkManager, Service {
@Override
public NetworkStatus getNetworkStatus() {
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected();
boolean wifi = false, ipv6Only = false;
if (connected) {
wifi = net.getType() == TYPE_WIFI;
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
else ipv6Only = areAllAvailableNetworksIpv6Only();
// https://issuetracker.google.com/issues/175055271
try {
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected();
boolean wifi = false, ipv6Only = false;
if (connected) {
wifi = net.getType() == TYPE_WIFI;
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
else ipv6Only = areAllAvailableNetworksIpv6Only();
}
return new NetworkStatus(connected, wifi, ipv6Only);
} catch (SecurityException e) {
logException(LOG, WARNING, e);
// Without the ConnectivityManager we can't detect whether we have
// internet access. Assume we do, which is probably less harmful
// than assuming we don't. Likewise, assume the connection is
// IPv6-only. Fall back to the WifiManager to detect whether we
// have a wifi connection.
LOG.info("ConnectivityManager is broken, guessing connectivity");
boolean connected = true, wifi = false, ipv6Only = true;
WifiManager wm = (WifiManager) app.getSystemService(WIFI_SERVICE);
if (wm != null) {
WifiInfo info = wm.getConnectionInfo();
if (info != null && info.getIpAddress() != 0) {
LOG.info("Connected to wifi");
wifi = true;
ipv6Only = false;
}
}
return new NetworkStatus(connected, wifi, ipv6Only);
}
return new NetworkStatus(connected, wifi, ipv6Only);
}
/**
@@ -130,23 +155,29 @@ class AndroidNetworkManager implements NetworkManager, Service {
*/
@TargetApi(23)
private boolean isActiveNetworkIpv6Only() {
Network net = connectivityManager.getActiveNetwork();
if (net == null) {
LOG.info("No active network");
// https://issuetracker.google.com/issues/175055271
try {
Network net = connectivityManager.getActiveNetwork();
if (net == null) {
LOG.info("No active network");
return false;
}
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) {
LOG.info("No link properties for active network");
return false;
}
boolean hasIpv6Unicast = false;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
return hasIpv6Unicast;
} catch (SecurityException e) {
logException(LOG, WARNING, e);
return false;
}
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) {
LOG.info("No link properties for active network");
return false;
}
boolean hasIpv6Unicast = false;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
return hasIpv6Unicast;
}
/**

View File

@@ -32,13 +32,22 @@ class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
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));
try {
return app.getContentResolver().openInputStream(Uri.parse(uri));
} catch (SecurityException e) {
throw new IOException(e);
}
}
@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));
try {
return app.getContentResolver()
.openOutputStream(Uri.parse(uri), "wt");
} catch (SecurityException e) {
throw new IOException(e);
}
}
}

View File

@@ -175,16 +175,24 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
@TargetApi(21)
@Nullable
private InetAddress getWifiClientIpv6Address() {
for (Network net : connectivityManager.getAllNetworks()) {
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) continue;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (isIpv6LinkLocalAddress(addr)) return addr;
// https://issuetracker.google.com/issues/175055271
try {
for (Network net : connectivityManager.getAllNetworks()) {
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) {
continue;
}
LinkProperties props =
connectivityManager.getLinkProperties(net);
if (props == null) continue;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (isIpv6LinkLocalAddress(addr)) return addr;
}
}
} catch (SecurityException e) {
logException(LOG, WARNING, e);
}
return null;
}
@@ -227,12 +235,17 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
// network's socket factory may try to connect via another network
private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault();
for (Network net : connectivityManager.getAllNetworks()) {
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
return net.getSocketFactory();
// https://issuetracker.google.com/issues/175055271
try {
for (Network net : connectivityManager.getAllNetworks()) {
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
return net.getSocketFactory();
}
}
} catch (SecurityException e) {
logException(LOG, WARNING, e);
}
LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault();

View File

@@ -11,62 +11,35 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedArchitectures;
@Immutable
@NotNullByDefault
public class AndroidTorPluginFactory implements DuplexPluginFactory {
public class AndroidTorPluginFactory extends TorPluginFactory {
private static final Logger LOG =
Logger.getLogger(AndroidTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor, wakefulIoExecutor;
private final Application app;
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final EventBus eventBus;
private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
private final ResourceProvider resourceProvider;
private final CircumventionProvider circumventionProvider;
private final BatteryManager batteryManager;
private final AndroidWakeLockManager wakeLockManager;
private final Clock clock;
private final File torDirectory;
private int torSocksPort;
private int torControlPort;
private final CryptoComponent crypto;
@Inject
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
Application app,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
@@ -75,80 +48,43 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager,
Clock clock,
CryptoComponent crypto,
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort,
CryptoComponent crypto) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
Application app,
AndroidWakeLockManager wakeLockManager) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
this.app = app;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.wakeLockManager = wakeLockManager;
this.clock = clock;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
this.crypto = crypto;
}
@Nullable
@Override
public TransportId getId() {
return TorConstants.ID;
}
@Override
public long getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
// Check that we have a Tor binary for this architecture
String architecture = null;
for (String abi : AndroidUtils.getSupportedArchitectures()) {
if (abi.startsWith("x86_64")) {
architecture = "x86_64";
break;
} else if (abi.startsWith("x86")) {
architecture = "x86";
break;
} else if (abi.startsWith("arm64")) {
architecture = "arm64";
break;
} else if (abi.startsWith("armeabi")) {
architecture = "arm";
break;
}
String getArchitectureForTorBinary() {
for (String abi : getSupportedArchitectures()) {
if (abi.startsWith("x86_64")) return "x86_64_pie";
else if (abi.startsWith("x86")) return "x86_pie";
else if (abi.startsWith("arm64")) return "arm64_pie";
else if (abi.startsWith("armeabi")) return "arm_pie";
}
if (architecture == null) {
LOG.info("Tor is not supported on this architecture");
return null;
}
// Use position-independent executable
architecture += "_pie";
return null;
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto);
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
@Override
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
return new AndroidTorPlugin(ioExecutor,
wakefulIoExecutor, app, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, wakeLockManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
eventBus.addListener(plugin);
return plugin;
}
}

View File

@@ -8,6 +8,7 @@ import android.content.Intent;
import android.os.Process;
import android.os.SystemClock;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AlarmListener;
@@ -116,10 +117,12 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
long dueMillis = now + MILLISECONDS.convert(delay, unit);
Runnable wakeful = () ->
wakeLockManager.executeWakefully(task, executor, "TaskHandoff");
Future<?> check = scheduleCheckForDueTasks(delay, unit);
ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check,
cancelled);
// Acquire the lock before scheduling the check to ensure the check
// doesn't access the task queue before the task has been added
ScheduledTask s;
synchronized (lock) {
Future<?> check = scheduleCheckForDueTasks(delay, unit);
s = new ScheduledTask(wakeful, dueMillis, check, cancelled);
tasks.add(s);
}
return s;
@@ -136,6 +139,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
return schedule(wrapped, executor, delay, unit, cancelled);
}
@GuardedBy("lock")
private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) {
Runnable wakeful = () -> wakeLockManager.runWakefully(
this::runDueTasks, "TaskScheduler");
@@ -206,7 +210,7 @@ class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
private final Future<?> check;
private final AtomicBoolean cancelled;
public ScheduledTask(Runnable task, long dueMillis,
private ScheduledTask(Runnable task, long dueMillis,
Future<?> check, AtomicBoolean cancelled) {
this.task = task;
this.dueMillis = dueMillis;

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.provider.Settings;
import org.briarproject.bramble.api.Pair;
@@ -134,4 +135,8 @@ public class AndroidUtils {
return null;
}
}
public static boolean isUiThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}

View File

@@ -87,8 +87,8 @@ dependencyVerification {
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
'org.briarproject:tor-android:0.3.5.17:tor-android-0.3.5.17.jar:1888afc10a26b93d00a010ea27bf0b1b162a6d524688b08b98d70d14dc363b54',
'org.briarproject:obfs4proxy-android:0.0.12:obfs4proxy-android-0.0.12.jar:84159d2a4668abc40e3fccaa1f6fa0c04892863f9eb80a866ac8928d9f9a7e89',
'org.briarproject:tor-android:0.4.5.12-2:tor-android-0.4.5.12-2.jar:8545dbcef2bb6aa89c32bb6f8ac51f7a64bce3ae85845b3578ffdeb9b206feb9',
'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:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble.api;
public interface Cancellable {
void cancel();
}

View File

@@ -1,8 +1,14 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.util.StringUtils;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
public abstract class StringMap extends Hashtable<String, String> {
protected StringMap(Map<String, String> m) {
@@ -52,4 +58,31 @@ public abstract class StringMap extends Hashtable<String, String> {
public void putLong(String key, long value) {
put(key, String.valueOf(value));
}
@Nullable
public int[] getIntArray(String key) {
String s = get(key);
if (s == null) return null;
// Handle empty string because "".split(",") returns {""}
if (s.length() == 0) return new int[0];
String[] intStrings = s.split(",");
int[] ints = new int[intStrings.length];
try {
for (int i = 0; i < ints.length; i++) {
ints[i] = Integer.parseInt(intStrings[i]);
}
} catch (NumberFormatException e) {
return null;
}
return ints;
}
public void putIntArray(String key, int[] value) {
List<String> intStrings = new ArrayList<>();
for (int integer : value) {
intStrings.add(String.valueOf(integer));
}
// Puts empty string if input array value is empty
put(key, StringUtils.join(intStrings, ","));
}
}

View File

@@ -6,14 +6,14 @@ import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public abstract class UniqueId extends Bytes {
public class UniqueId extends Bytes {
/**
* The length of a unique identifier in bytes.
*/
public static final int LENGTH = 32;
protected UniqueId(byte[] id) {
public UniqueId(byte[] id) {
super(id);
if (id.length != LENGTH) throw new IllegalArgumentException();
}

View File

@@ -9,6 +9,8 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
@@ -18,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@NotNullByDefault
@@ -123,6 +126,19 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
BdfDictionary properties) throws FormatException;
/**
* Parse and validate the elements of a Mailbox update message.
*
* @return the parsed update message
* @throws FormatException if the message elements are invalid
*/
MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
BdfList serverSupports, BdfDictionary properties)
throws FormatException;
List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
throws FormatException;
/**
* Retrieves the contact ID from the group metadata of the given contact
* group.

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
@@ -17,9 +16,4 @@ public class PendingContactId extends UniqueId {
public PendingContactId(byte[] id) {
super(id);
}
@Override
public boolean equals(@Nullable Object o) {
return o instanceof PendingContactId && super.equals(o);
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
@@ -10,6 +11,8 @@ import javax.annotation.Nullable;
@NotNullByDefault
public interface CryptoComponent {
UniqueId generateUniqueId();
SecretKey generateSecretKey();
SecureRandom getSecureRandom();
@@ -172,9 +175,11 @@ public interface CryptoComponent {
String asciiArmour(byte[] b, int lineLength);
/**
* Encode the onion/hidden service address given its public key. As
* specified here: https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
* Encode the Onion given its public key. Specified here:
* https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt?id=29245fd5#n2135
*
* @return the encoded onion, base32 chars
*/
String encodeOnionAddress(byte[] publicKey);
String encodeOnion(byte[] publicKey);
}

View File

@@ -33,11 +33,18 @@ import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* Encapsulates the database implementation and exposes high-level operations
* to other components.
* <p>
* With the exception of the {@link #open(SecretKey, MigrationListener)} and
* {@link #close()} methods, which must not be called concurrently, the
* database can be accessed from any thread. See {@link TransactionManager}
* for locking behaviour.
*/
@ThreadSafe
@NotNullByDefault
public interface DatabaseComponent extends TransactionManager {
@@ -193,26 +200,15 @@ public interface DatabaseComponent extends TransactionManager {
throws DbException;
/**
* Returns a batch of messages for the given contact, with a total length
* less than or equal to the given length, for transmission over a
* transport with the given maximum latency. Returns null if there are no
* sendable messages that fit in the given length.
* Returns a batch of messages for the given contact, for transmission over
* a transport with the given maximum latency. The total length of the
* messages, including record headers, will be no more than the given
* capacity. Returns null if there are no sendable messages that would fit
* in the given capacity.
*/
@Nullable
Collection<Message> generateBatch(Transaction txn, ContactId c,
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;
long capacity, long maxLatency) throws DbException;
/**
* Returns an offer for the given contact for transmission over a
@@ -232,15 +228,16 @@ public interface DatabaseComponent extends TransactionManager {
throws DbException;
/**
* Returns a batch of messages for the given contact, with a total length
* less than or equal to the given length, for transmission over a
* transport with the given maximum latency. Only messages that have been
* requested by the contact are returned. Returns null if there are no
* sendable messages that fit in the given length.
* Returns a batch of messages for the given contact, for transmission over
* a transport with the given maximum latency. Only messages that have been
* requested by the contact are returned. The total length of the messages,
* including record headers, will be no more than the given capacity.
* Returns null if there are no sendable messages that have been requested
* by the contact and would fit in the given capacity.
*/
@Nullable
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
int maxLength, long maxLatency) throws DbException;
long capacity, long maxLatency) throws DbException;
/**
* Returns the contact with the given ID.
@@ -344,6 +341,30 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
Metadata query) throws DbException;
/**
* Returns the IDs of some messages received from the given contact that
* need to be acknowledged, up to the given number of messages.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToAck(Transaction txn, ContactId c,
int maxMessages) throws DbException;
/**
* Returns the IDs of some messages that are eligible to be sent to the
* given contact over a transport with the given maximum latency. The total
* length of the messages including record headers will be no more than the
* given capacity.
* <p/>
* Unlike {@link #getUnackedMessagesToSend(Transaction, ContactId)} this
* method does not return messages that have already been sent unless they
* are due for retransmission.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToSend(Transaction txn, ContactId c,
long capacity, long maxLatency) throws DbException;
/**
* Returns the IDs of any messages that need to be validated.
* <p/>
@@ -460,21 +481,36 @@ public interface DatabaseComponent extends TransactionManager {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException;
/**
* Returns the message with the given ID for transmission to the given
* contact over a transport with the given maximum latency. Returns null
* if the message is no longer visible to the contact.
*
* @param markAsSent True if the message should be marked as sent.
* If false it can be marked as sent by calling
* {@link #setMessagesSent(Transaction, ContactId, Collection, long)}.
*/
@Nullable
Message getMessageToSend(Transaction txn, ContactId c, MessageId m,
long maxLatency, boolean markAsSent) 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.
* given contact.
* <p>
* Unlike {@link #getMessagesToSend(Transaction, ContactId, long, 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(Transaction txn,
Collection<MessageId> getUnackedMessagesToSend(Transaction txn,
ContactId c) throws DbException;
/**
* Reset the transmission count, expiry time and ETA of all messages that
* are eligible to be sent to the given contact. This includes messages that
* have already been sent and are not yet due for retransmission.
* Resets the transmission count, expiry time and max latency of all messages
* that are eligible to be sent to the given contact. This includes messages
* that have already been sent and are not yet due for retransmission.
*/
void resetUnackedMessagesToSend(Transaction txn, ContactId c)
throws DbException;
@@ -648,6 +684,13 @@ public interface DatabaseComponent extends TransactionManager {
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
throws DbException;
/**
* Records an ack for the given messages as having been sent to the given
* contact.
*/
void setAckSent(Transaction txn, ContactId c, Collection<MessageId> acked)
throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
@@ -694,6 +737,13 @@ public interface DatabaseComponent extends TransactionManager {
void setMessageState(Transaction txn, MessageId m, MessageState state)
throws DbException;
/**
* Records the given messages as having been sent to the given contact
* over a transport with the given maximum latency.
*/
void setMessagesSent(Transaction txn, ContactId c,
Collection<MessageId> sent, long maxLatency) throws DbException;
/**
* Adds dependencies for a message
*/

View File

@@ -18,6 +18,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* submitted, tasks are not run concurrently, and submitting a task will never
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
* discarded.
* <p>
* It is not mandatory to use this executor for database tasks. The database
* can be accessed from any thread, but this executor's guarantee that tasks
* are run in the order they're submitted may be useful in some cases.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})

View File

@@ -45,6 +45,9 @@ public class Transaction {
/**
* Attaches an event to be broadcast when the transaction has been
* committed. The event will be broadcast on the {@link EventExecutor}.
* Events and {@link #attach(Runnable) tasks} are submitted to the
* {@link EventExecutor} in the order they were attached to the
* transaction.
*/
public void attach(Event e) {
if (actions == null) actions = new ArrayList<>();
@@ -54,6 +57,9 @@ public class Transaction {
/**
* Attaches a task to be executed when the transaction has been
* committed. The task will be run on the {@link EventExecutor}.
* {@link #attach(Event) Events} and tasks are submitted to the
* {@link EventExecutor} in the order they were attached to the
* transaction.
*/
public void attach(Runnable r) {
if (actions == null) actions = new ArrayList<>();

View File

@@ -1,51 +1,95 @@
package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* An interface for managing database transactions.
* <p>
* Read-only transactions may access the database concurrently. Read-write
* transactions access the database exclusively, so starting a read-only or
* read-write transaction will block until there are no read-write
* transactions in progress.
* <p>
* Failing to {@link #endTransaction(Transaction) end} a transaction will
* prevent other callers from accessing the database, so it is recommended to
* use the {@link #transaction(boolean, DbRunnable)},
* {@link #transactionWithResult(boolean, DbCallable)} and
* {@link #transactionWithNullableResult(boolean, NullableDbCallable)} methods
* where possible, which handle committing or aborting the transaction on the
* caller's behalf.
* <p>
* Transactions are not reentrant, i.e. it is not permitted to start a
* transaction on a thread that already has a transaction in progress.
*/
@ThreadSafe
@NotNullByDefault
public interface TransactionManager {
/**
* Starts a new transaction and returns an object representing it.
* <p/>
* This method acquires locks, so it must not be called while holding a
* lock.
* Starts a new transaction and returns an object representing it. This
* method acquires the database lock, which is held until
* {@link #endTransaction(Transaction)} is called.
*
* @param readOnly true if the transaction will only be used for reading.
* @param readOnly True if the transaction will only be used for reading,
* in which case the database lock can be shared with other read-only
* transactions.
*/
Transaction startTransaction(boolean readOnly) throws DbException;
/**
* Commits a transaction to the database.
* {@link #endTransaction(Transaction)} must be called to release the
* database lock.
*/
void commitTransaction(Transaction txn) throws DbException;
/**
* Ends a transaction. If the transaction has not been committed,
* it will be aborted. If the transaction has been committed,
* any events attached to the transaction are broadcast.
* The database lock will be released in either case.
* Ends a transaction. If the transaction has not been committed by
* calling {@link #commitTransaction(Transaction)}, it is aborted and the
* database lock is released.
* <p>
* If the transaction has been committed, any
* {@link Transaction#attach events} attached to the transaction are
* broadcast and any {@link Transaction#attach(Runnable) tasks} attached
* to the transaction are submitted to the {@link EventExecutor}. The
* database lock is then released.
*/
void endTransaction(Transaction txn);
/**
* Runs the given task within a transaction.
* Runs the given task within a transaction. The database lock is held
* while running the task.
*
* @param readOnly True if the transaction will only be used for reading,
* in which case the database lock can be shared with other read-only
* transactions.
*/
<E extends Exception> void transaction(boolean readOnly,
DbRunnable<E> task) throws DbException, E;
/**
* Runs the given task within a transaction and returns the result of the
* task.
* task. The database lock is held while running the task.
*
* @param readOnly True if the transaction will only be used for reading,
* in which case the database lock can be shared with other read-only
* transactions.
*/
<R, E extends Exception> R transactionWithResult(boolean readOnly,
DbCallable<R, E> task) throws DbException, E;
/**
* Runs the given task within a transaction and returns the result of the
* task, which may be null.
* task, which may be null. The database lock is held while running the
* task.
*
* @param readOnly True if the transaction will only be used for reading,
* in which case the database lock can be shared with other read-only
* transactions.
*/
@Nullable
<R, E extends Exception> R transactionWithNullableResult(boolean readOnly,

View File

@@ -21,9 +21,4 @@ public class AuthorId extends UniqueId {
public AuthorId(byte[] id) {
super(id);
}
@Override
public boolean equals(Object o) {
return o instanceof AuthorId && super.equals(o);
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.bramble.api.mailbox;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
public interface MailboxConstants {
/**
* The maximum length of a file that can be uploaded to or downloaded from
* a mailbox.
*/
int MAX_FILE_BYTES = 1024 * 1024;
/**
* The maximum length of the plaintext payload of a file, such that the
* ciphertext is no more than {@link #MAX_FILE_BYTES}.
*/
int MAX_FILE_PAYLOAD_BYTES =
(MAX_FILE_BYTES - TAG_LENGTH - STREAM_HEADER_LENGTH)
/ MAX_FRAME_LENGTH * MAX_PAYLOAD_LENGTH;
/**
* The number of connection failures
* that indicate a problem with the mailbox.
*/
int PROBLEM_NUM_CONNECTION_FAILURES = 5;
/**
* The time in milliseconds since the last connection success
* that need to pass to indicates a problem with the mailbox.
*/
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
}

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import javax.annotation.Nullable;
@@ -32,4 +34,23 @@ public interface MailboxManager {
*/
MailboxPairingTask startPairingTask(String qrCodePayload);
/**
* Can be used by the UI to test the mailbox connection.
*
* @return true (success) or false (error).
* A {@link OwnMailboxConnectionStatusEvent} might be broadcast with a new
* {@link MailboxStatus}.
*/
boolean checkConnection();
/**
* Unpairs the owner's mailbox and tries to wipe it.
* As this makes a network call, it should be run on the {@link IoExecutor}.
*
* @return true if we could wipe the mailbox, false if we couldn't.
* It is advised to inform the user to wipe the mailbox themselves,
* if we failed to wipe it.
*/
@IoExecutor
boolean unPair() throws DbException;
}

View File

@@ -1,59 +1,25 @@
package org.briarproject.bramble.api.mailbox;
import javax.annotation.Nullable;
public abstract class MailboxPairingState {
/**
* The QR code payload that was scanned by the user.
* This is null if the code should not be re-used anymore in this state.
*/
@Nullable
public final String qrCodePayload;
MailboxPairingState(@Nullable String qrCodePayload) {
this.qrCodePayload = qrCodePayload;
}
public static class QrCodeReceived extends MailboxPairingState {
public QrCodeReceived(String qrCodePayload) {
super(qrCodePayload);
}
}
public static class Pairing extends MailboxPairingState {
public Pairing(String qrCodePayload) {
super(qrCodePayload);
}
}
public static class Paired extends MailboxPairingState {
public Paired() {
super(null);
}
}
public static class InvalidQrCode extends MailboxPairingState {
public InvalidQrCode() {
super(null);
}
}
public static class MailboxAlreadyPaired extends MailboxPairingState {
public MailboxAlreadyPaired() {
super(null);
}
}
public static class ConnectionError extends MailboxPairingState {
public ConnectionError(String qrCodePayload) {
super(qrCodePayload);
}
}
public static class UnexpectedError extends MailboxPairingState {
public UnexpectedError(String qrCodePayload) {
super(qrCodePayload);
}
}
}

View File

@@ -2,6 +2,9 @@ package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@@ -11,18 +14,48 @@ public class MailboxProperties {
private final String baseUrl;
private final MailboxAuthToken authToken;
private final boolean owner;
private final List<MailboxVersion> serverSupports;
@Nullable
private final MailboxFolderId inboxId; // Null for own mailbox
@Nullable
private final MailboxFolderId outboxId; // Null for own mailbox
/**
* Constructor for properties used by the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
boolean owner) {
List<MailboxVersion> serverSupports) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.owner = owner;
this.owner = true;
this.serverSupports = serverSupports;
this.inboxId = null;
this.outboxId = null;
}
/**
* Constructor for properties used by a contact of the mailbox's owner.
*/
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
List<MailboxVersion> serverSupports, MailboxFolderId inboxId,
MailboxFolderId outboxId) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.owner = false;
this.serverSupports = serverSupports;
this.inboxId = inboxId;
this.outboxId = outboxId;
}
public String getBaseUrl() {
return baseUrl;
}
public String getOnion() {
return baseUrl.replaceFirst("^http://", "")
.replaceFirst("\\.onion$", "");
}
public MailboxAuthToken getAuthToken() {
return authToken;
}
@@ -30,4 +63,18 @@ public class MailboxProperties {
public boolean isOwner() {
return owner;
}
public List<MailboxVersion> getServerSupports() {
return serverSupports;
}
@Nullable
public MailboxFolderId getInboxId() {
return inboxId;
}
@Nullable
public MailboxFolderId getOutboxId() {
return outboxId;
}
}

View File

@@ -1,15 +1,26 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.Nullable;
@NotNullByDefault
public interface MailboxSettingsManager {
/**
* Registers a hook to be called when a mailbox has been paired or unpaired.
* This method should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerMailboxHook(MailboxHook hook);
@Nullable
MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException;
@@ -17,6 +28,8 @@ public interface MailboxSettingsManager {
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException;
void removeOwnMailboxProperties(Transaction txn) throws DbException;
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
void recordSuccessfulConnection(Transaction txn, long now)
@@ -30,4 +43,23 @@ public interface MailboxSettingsManager {
@Nullable
String getPendingUpload(Transaction txn, ContactId id) throws DbException;
interface MailboxHook {
/**
* Called when Briar is paired with a mailbox
*
* @param txn A read-write transaction
* @param ownOnion Our new mailbox's onion (56 base32 chars)
*/
void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports)
throws DbException;
/**
* Called when the mailbox is unpaired
*
* @param txn A read-write transaction
*/
void mailboxUnpaired(Transaction txn) throws DbException;
}
}

View File

@@ -4,6 +4,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_MS_SINCE_LAST_SUCCESS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.PROBLEM_NUM_CONNECTION_FAILURES;
@Immutable
@NotNullByDefault
public class MailboxStatus {
@@ -56,4 +59,12 @@ public class MailboxStatus {
public int getAttemptsSinceSuccess() {
return attemptsSinceSuccess;
}
/**
* @return true if this status indicates a problem with the mailbox.
*/
public boolean hasProblem(long now) {
return attemptsSinceSuccess >= PROBLEM_NUM_CONNECTION_FAILURES &&
(now - lastSuccess) >= PROBLEM_MS_SINCE_LAST_SUCCESS;
}
}

View File

@@ -0,0 +1,31 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxUpdate {
private final boolean hasMailbox;
private final List<MailboxVersion> clientSupports;
public MailboxUpdate(List<MailboxVersion> clientSupports) {
this(clientSupports, false);
}
MailboxUpdate(List<MailboxVersion> clientSupports, boolean hasMailbox) {
this.clientSupports = clientSupports;
this.hasMailbox = hasMailbox;
}
public List<MailboxVersion> getClientSupports() {
return clientSupports;
}
public boolean hasMailbox() {
return hasMailbox;
}
}

View File

@@ -0,0 +1,72 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import javax.annotation.Nullable;
@NotNullByDefault
public interface MailboxUpdateManager {
/**
* The unique ID of the mailbox update (properties) client.
*/
ClientId CLIENT_ID =
new ClientId("org.briarproject.bramble.mailbox.properties");
/**
* The current major version of the mailbox update (properties) client.
*/
int MAJOR_VERSION = 2;
/**
* The current minor version of the mailbox update (properties) client.
*/
int MINOR_VERSION = 0;
/**
* The number of properties required for an update message with a mailbox.
*/
int PROP_COUNT = 4;
/**
* The required properties of an update message with a mailbox.
*/
String PROP_KEY_ONION = "onion";
String PROP_KEY_AUTHTOKEN = "authToken";
String PROP_KEY_INBOXID = "inboxId";
String PROP_KEY_OUTBOXID = "outboxId";
/**
* Length of the Onion property.
*/
int PROP_ONION_LENGTH = 56;
/**
* Message metadata key for the version number of a local or remote update,
* as a BDF long.
*/
String MSG_KEY_VERSION = "version";
/**
* Message metadata key for whether an update is local or remote, as a BDF
* boolean.
*/
String MSG_KEY_LOCAL = "local";
/**
* Key in the client's local group for storing the clientSupports list that
* was last sent out.
*/
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException;
@Nullable
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException;
}

View File

@@ -0,0 +1,30 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxUpdateWithMailbox extends MailboxUpdate {
private final MailboxProperties properties;
public MailboxUpdateWithMailbox(List<MailboxVersion> clientSupports,
MailboxProperties properties) {
super(clientSupports, true);
if (properties.isOwner()) throw new IllegalArgumentException();
this.properties = properties;
}
public MailboxUpdateWithMailbox(MailboxUpdateWithMailbox o,
List<MailboxVersion> newClientSupports) {
this(newClientSupports, o.getMailboxProperties());
}
public MailboxProperties getMailboxProperties() {
return properties;
}
}

View File

@@ -0,0 +1,44 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxVersion implements Comparable<MailboxVersion> {
private final int major;
private final int minor;
public MailboxVersion(int major, int minor) {
this.major = major;
this.minor = minor;
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
@Override
public boolean equals(Object o) {
if (o instanceof MailboxVersion) {
MailboxVersion v = (MailboxVersion) o;
return major == v.major && minor == v.minor;
}
return false;
}
@Override
public int compareTo(MailboxVersion v) {
int c = major - v.major;
if (c != 0) {
return c;
}
return minor - v.minor;
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast by {@link MailboxSettingsManager} when
* recording a connection failure for own Mailbox
* that has persistent for long enough for the mailbox owner to become active
* and fix the problem with the mailbox.
*/
@Immutable
@NotNullByDefault
public class MailboxProblemEvent extends Event {
}

View File

@@ -0,0 +1,27 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast by {@link MailboxSettingsManager} when
* recording the connection status of own Mailbox.
*/
@Immutable
@NotNullByDefault
public class OwnMailboxConnectionStatusEvent extends Event {
private final MailboxStatus status;
public OwnMailboxConnectionStatusEvent(MailboxStatus status) {
this.status = status;
}
public MailboxStatus getStatus() {
return status;
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.api.mailbox.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when {@link MailboxUpdate} are received
* from a contact.
*/
@Immutable
@NotNullByDefault
public class RemoteMailboxUpdateEvent extends Event {
private final ContactId contactId;
private final MailboxUpdate mailboxUpdate;
public RemoteMailboxUpdateEvent(ContactId contactId,
MailboxUpdate mailboxUpdate) {
this.contactId = contactId;
this.mailboxUpdate = mailboxUpdate;
}
public ContactId getContact() {
return contactId;
}
public MailboxUpdate getMailboxUpdate() {
return mailboxUpdate;
}
}

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.api.plugin;
import static java.util.concurrent.TimeUnit.SECONDS;
public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor");
@@ -10,8 +12,9 @@ public interface TorConstants {
int DEFAULT_SOCKS_PORT = 59050;
int DEFAULT_CONTROL_PORT = 59051;
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
int CONNECT_TO_PROXY_TIMEOUT = (int) SECONDS.toMillis(5);
int EXTRA_CONNECT_TIMEOUT = (int) SECONDS.toMillis(120);
int EXTRA_SOCKET_TIMEOUT = (int) SECONDS.toMillis(30);
// Local settings (not shared with contacts)
String PREF_TOR_NETWORK = "network2";

View File

@@ -12,4 +12,6 @@ public interface RecordWriter {
void flush() throws IOException;
void close() throws IOException;
long getBytesWritten();
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* An interface for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession} so they can be recorded in the DB as sent
* or acked at some later time.
*/
public interface DeferredSendHandler {
void onAckSent(Collection<MessageId> acked);
void onMessageSent(MessageId sent);
}

View File

@@ -20,9 +20,4 @@ public class GroupId extends UniqueId {
public GroupId(byte[] id) {
super(id);
}
@Override
public boolean equals(Object o) {
return o instanceof GroupId && super.equals(o);
}
}

View File

@@ -27,9 +27,4 @@ public class MessageId extends UniqueId {
public MessageId(byte[] id) {
super(id);
}
@Override
public boolean equals(Object o) {
return o instanceof MessageId && super.equals(o);
}
}

View File

@@ -20,4 +20,6 @@ public interface SyncRecordWriter {
void writePriority(Priority p) throws IOException;
void flush() throws IOException;
long getBytesWritten();
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
@@ -16,6 +17,8 @@ public interface TaskScheduler {
* <p>
* If the platform supports wake locks, a wake lock will be held while
* submitting and running the task.
*
* @return A {@link Cancellable} for cancelling the task.
*/
Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit);
@@ -27,17 +30,11 @@ public interface TaskScheduler {
* <p>
* If the platform supports wake locks, a wake lock will be held while
* submitting and running the task.
*
* @return A {@link Cancellable} for cancelling all future executions of
* the task.
*/
Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit);
interface Cancellable {
/**
* Cancels the task if it has not already started running. If the task
* is {@link #scheduleWithFixedDelay(Runnable, Executor, long, long, TimeUnit) periodic},
* all future executions of the task are cancelled.
*/
void cancel();
}
}

View File

@@ -26,7 +26,7 @@ public class NetworkUtils {
// Despite what the docs say, the return value can be null
//noinspection ConstantConditions
return ifaces == null ? emptyList() : list(ifaces);
} catch (SocketException e) {
} catch (SocketException | NullPointerException e) {
logException(LOG, WARNING, e);
return emptyList();
}

View File

@@ -1,17 +1,46 @@
package org.briarproject.bramble.test;
import java.lang.Thread.UncaughtExceptionHandler;
import org.junit.After;
import org.junit.Before;
import static org.junit.Assert.fail;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
public abstract class BrambleTestCase {
private static final Logger LOG =
getLogger(BrambleTestCase.class.getName());
@Nullable
protected volatile Throwable exceptionInBackgroundThread = null;
public BrambleTestCase() {
// Ensure exceptions thrown on worker threads cause tests to fail
UncaughtExceptionHandler fail = (thread, throwable) -> {
throwable.printStackTrace();
fail();
LOG.log(WARNING, "Caught unhandled exception", throwable);
exceptionInBackgroundThread = throwable;
};
Thread.setDefaultUncaughtExceptionHandler(fail);
}
@Before
public void beforeBrambleTestCase() {
exceptionInBackgroundThread = null;
}
@After
public void afterBrambleTestCase() {
Throwable thrown = exceptionInBackgroundThread;
if (thrown != null) {
LOG.log(WARNING,
"Background thread has thrown an exception unexpectedly",
thrown);
throw new AssertionError(thrown);
}
}
}

View File

@@ -12,10 +12,20 @@ import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.db.CommitAction;
import org.briarproject.bramble.api.db.EventAction;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.ClientId;
@@ -39,6 +49,8 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
@@ -214,6 +226,19 @@ public class TestUtils {
getAgreementPublicKey(), verified);
}
public static MailboxProperties getMailboxProperties(boolean owner,
List<MailboxVersion> serverSupports) {
String baseUrl = "http://" + getRandomString(56) + ".onion"; // TODO
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
if (owner) {
return new MailboxProperties(baseUrl, authToken, serverSupports);
}
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxProperties(baseUrl, authToken, serverSupports,
inboxId, outboxId);
}
public static void writeBytes(File file, byte[] bytes)
throws IOException {
FileOutputStream outputStream = new FileOutputStream(file);
@@ -266,9 +291,48 @@ public class TestUtils {
return Math.sqrt(getVariance(samples));
}
public static boolean isOptionalTestEnabled(Class testClass) {
public static boolean isOptionalTestEnabled(Class<?> testClass) {
String optionalTests = System.getenv("OPTIONAL_TESTS");
return optionalTests != null &&
asList(optionalTests.split(",")).contains(testClass.getName());
}
public static boolean mailboxUpdateEqual(@Nullable MailboxUpdate a,
@Nullable MailboxUpdate b) {
if (a == null || b == null) {
return a == b;
}
if (!a.hasMailbox() && !b.hasMailbox()) {
return a.getClientSupports().equals(b.getClientSupports());
} else if (a.hasMailbox() && b.hasMailbox()) {
MailboxUpdateWithMailbox am = (MailboxUpdateWithMailbox) a;
MailboxUpdateWithMailbox bm = (MailboxUpdateWithMailbox) b;
return am.getClientSupports().equals(bm.getClientSupports()) &&
mailboxPropertiesEqual(am.getMailboxProperties(),
bm.getMailboxProperties());
}
return false;
}
public static boolean mailboxPropertiesEqual(@Nullable MailboxProperties a,
@Nullable MailboxProperties b) {
if (a == null || b == null) {
return a == b;
}
return a.getOnion().equals(b.getOnion()) &&
a.getAuthToken().equals(b.getAuthToken()) &&
a.isOwner() == b.isOwner() &&
a.getServerSupports().equals(b.getServerSupports());
}
public static boolean hasEvent(Transaction txn,
Class<? extends Event> eventClass) {
for (CommitAction action : txn.getActions()) {
if (action instanceof EventAction) {
Event event = ((EventAction) action).getEvent();
if (eventClass.isInstance(event)) return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,36 @@
package org.briarproject.bramble.test;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class ThreadExceptionTest extends BrambleTestCase {
@Test(expected = AssertionError.class)
public void testAssertionErrorMakesTestCaseFail() {
// This is what BrambleTestCase does, too:
fail();
}
@Test
public void testExceptionInThreadMakesTestCaseFail() {
Thread t = new Thread(() -> {
System.out.println("thread before exception");
throw new RuntimeException("boom");
});
t.start();
try {
t.join();
System.out.println("joined thread");
} catch (InterruptedException e) {
System.out.println("interrupted while joining thread");
fail();
}
assertNotNull(exceptionInBackgroundThread);
exceptionInBackgroundThread = null;
}
}

View File

@@ -1,6 +1,7 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.fasterxml.jackson.core:jackson-annotations:2.13.0:jackson-annotations-2.13.0.jar:81f9724d8843e8b08f8f6c0609e7a2b030d00c34861c4ac7e2099a7235047d6f',
'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.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',

View File

@@ -16,7 +16,7 @@ dependencies {
implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.3'
implementation 'org.briarproject:jtorctl:0.4'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.rendezvous.RendezvousModule;
@@ -28,6 +29,8 @@ public interface BrambleCoreEagerSingletons {
void inject(LifecycleModule.EagerSingletons init);
void inject(MailboxModule.EagerSingletons init);
void inject(PluginModule.EagerSingletons init);
void inject(PropertiesModule.EagerSingletons init);
@@ -51,6 +54,7 @@ public interface BrambleCoreEagerSingletons {
c.inject(new DatabaseExecutorModule.EagerSingletons());
c.inject(new IdentityModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons());
c.inject(new MailboxModule.EagerSingletons());
c.inject(new RendezvousModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons());

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
@@ -22,6 +23,12 @@ 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.AuthorFactory;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties;
@@ -29,13 +36,16 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.Base32;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -46,6 +56,12 @@ import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KE
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_COUNT;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_ONION_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
@@ -399,6 +415,69 @@ class ClientHelperImpl implements ClientHelper {
return tpMap;
}
@Override
public MailboxUpdate parseAndValidateMailboxUpdate(BdfList clientSupports,
BdfList serverSupports, BdfDictionary properties)
throws FormatException {
List<MailboxVersion> clientSupportsList =
parseMailboxVersionList(clientSupports);
List<MailboxVersion> serverSupportsList =
parseMailboxVersionList(serverSupports);
// We must always learn what Mailbox API version(s) the client supports
if (clientSupports.isEmpty()) {
throw new FormatException();
}
if (properties.isEmpty()) {
// No mailbox -- cannot claim to support any API versions!
if (!serverSupports.isEmpty()) {
throw new FormatException();
}
return new MailboxUpdate(clientSupportsList);
}
// Mailbox must be accompanied by the Mailbox API version(s) it supports
if (serverSupports.isEmpty()) {
throw new FormatException();
}
// Accepting more props than we need, for forward compatibility
if (properties.size() < PROP_COUNT) {
throw new FormatException();
}
String onion = properties.getString(PROP_KEY_ONION);
checkLength(onion, PROP_ONION_LENGTH);
try {
Base32.decode(onion, true);
} catch (IllegalArgumentException e) {
throw new FormatException();
}
byte[] authToken = properties.getRaw(PROP_KEY_AUTHTOKEN);
checkLength(authToken, UniqueId.LENGTH);
byte[] inboxId = properties.getRaw(PROP_KEY_INBOXID);
checkLength(inboxId, UniqueId.LENGTH);
byte[] outboxId = properties.getRaw(PROP_KEY_OUTBOXID);
checkLength(outboxId, UniqueId.LENGTH);
String baseUrl = "http://" + onion + ".onion"; // TODO
MailboxProperties props = new MailboxProperties(baseUrl,
new MailboxAuthToken(authToken), serverSupportsList,
new MailboxFolderId(inboxId), new MailboxFolderId(outboxId));
return new MailboxUpdateWithMailbox(clientSupportsList, props);
}
@Override
public List<MailboxVersion> parseMailboxVersionList(BdfList bdfList)
throws FormatException {
List<MailboxVersion> list = new ArrayList<>();
for (int i = 0; i < bdfList.size(); i++) {
BdfList element = bdfList.getList(i);
if (element.size() != 2) {
throw new FormatException();
}
list.add(new MailboxVersion(element.getLong(0).intValue(),
element.getLong(1).intValue()));
}
return list;
}
@Override
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {

View File

@@ -8,6 +8,7 @@ import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent;
@@ -41,6 +42,7 @@ import javax.inject.Inject;
import static java.lang.System.arraycopy;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
@@ -54,7 +56,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG =
Logger.getLogger(CryptoComponentImpl.class.getName());
getLogger(CryptoComponentImpl.class.getName());
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits
@@ -128,6 +130,13 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
@Override
public UniqueId generateUniqueId() {
byte[] b = new byte[UniqueId.LENGTH];
secureRandom.nextBytes(b);
return new UniqueId(b);
}
@Override
public SecretKey generateSecretKey() {
byte[] b = new byte[SecretKey.LENGTH];
@@ -449,7 +458,7 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public String encodeOnionAddress(byte[] publicKey) {
public String encodeOnion(byte[] publicKey) {
Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
digest.update(label, 0, label.length);

View File

@@ -406,6 +406,12 @@ interface Database<T> {
Collection<MessageId> getMessageIds(T txn, GroupId g, Metadata query)
throws DbException;
/**
* Returns the length of the given message in bytes, including the
* message header.
*/
int getMessageLength(T txn, MessageId m) throws DbException;
/**
* Returns the metadata for all delivered messages in the given group.
* <p/>
@@ -496,7 +502,8 @@ interface Database<T> {
/**
* Returns the IDs of some messages that are eligible to be sent to the
* given contact, up to the given total length.
* given contact. The total length of the messages including record headers
* will be no more than the given capacity.
* <p/>
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
* does not return messages that have already been sent unless they are
@@ -504,20 +511,20 @@ interface Database<T> {
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
Collection<MessageId> getMessagesToSend(T txn, ContactId c, long capacity,
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.
* given contact.
* <p/>
* Unlike {@link #getMessagesToSend(Object, ContactId, int, long)} this
* Unlike {@link #getMessagesToSend(Object, ContactId, long, 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)
Collection<MessageId> getUnackedMessagesToSend(T txn, ContactId c)
throws DbException;
/**
@@ -598,13 +605,14 @@ interface Database<T> {
/**
* Returns the IDs of some messages that are eligible to be sent to the
* given contact and have been requested by the contact, up to the given
* total length.
* given contact and have been requested by the contact. The total length
* of the messages including record headers will be no more than the given
* capacity.
* <p/>
* Read-only.
*/
Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
int maxLength, long maxLatency) throws DbException;
long capacity, long maxLatency) throws DbException;
/**
* Returns all settings in the given namespace.
@@ -758,9 +766,10 @@ interface Database<T> {
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
/**
* Resets the transmission count, expiry time and ETA of all messages that
* are eligible to be sent to the given contact. This includes messages that
* have already been sent and are not yet due for retransmission.
* Resets the transmission count, expiry time and max latency of all
* messages that are eligible to be sent to the given contact. This includes
* messages that have already been sent and are not yet due for
* retransmission.
*/
void resetUnackedMessagesToSend(T txn, ContactId c) throws DbException;
@@ -848,11 +857,13 @@ interface Database<T> {
void stopCleanupTimer(T txn, MessageId m) throws DbException;
/**
* Updates the transmission count, expiry time and estimated time of arrival
* of the given message with respect to the given contact, using the latency
* of the transport over which it was sent.
* Updates the transmission count, expiry time and max latency of the given
* message with respect to the given contact.
*
* @param maxLatency latency of the transport over which the message was
* sent.
*/
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m,
void updateRetransmissionData(T txn, ContactId c, MessageId m,
long maxLatency) throws DbException;
/**

View File

@@ -75,7 +75,6 @@ import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -87,6 +86,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -424,53 +424,27 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, int maxLength, long maxLatency) throws DbException {
ContactId c, long capacity, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getMessagesToSend(txn, c, maxLength, maxLatency);
db.getMessagesToSend(txn, c, capacity, maxLatency);
if (ids.isEmpty()) return null;
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
db.updateRetransmissionData(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, 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;
}
@Nullable
@Override
public Offer generateOffer(Transaction transaction, ContactId c,
@@ -483,7 +457,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.getMessagesToOffer(txn, c, maxMessages, maxLatency);
if (ids.isEmpty()) return null;
for (MessageId m : ids)
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
db.updateRetransmissionData(txn, c, m, maxLatency);
return new Offer(ids);
}
@@ -505,22 +479,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Nullable
@Override
public Collection<Message> generateRequestedBatch(Transaction transaction,
ContactId c, int maxLength, long maxLatency) throws DbException {
ContactId c, long capacity, long maxLatency) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
Collection<MessageId> ids =
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
db.getRequestedMessagesToSend(txn, c, capacity, maxLatency);
if (ids.isEmpty()) return null;
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) {
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
db.updateRetransmissionData(txn, c, m, maxLatency);
}
if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids, totalLength));
return messages;
@@ -635,6 +609,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageIds(txn, g, query);
}
@Override
public Collection<MessageId> getMessagesToAck(Transaction transaction,
ContactId c, int maxMessages) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getMessagesToAck(txn, c, maxMessages);
}
@Override
public Collection<MessageId> getMessagesToSend(Transaction transaction,
ContactId c, long capacity, long maxLatency) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getMessagesToSend(txn, c, capacity, maxLatency);
}
@Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException {
@@ -740,10 +732,29 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return status;
}
@Nullable
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(
Transaction transaction,
ContactId c) throws DbException {
public Message getMessageToSend(Transaction transaction, ContactId c,
MessageId m, long maxLatency, boolean markAsSent)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
if (!db.containsVisibleMessage(txn, c, m)) return null;
Message message = db.getMessage(txn, m);
if (markAsSent) {
db.updateRetransmissionData(txn, c, m, maxLatency);
db.lowerRequestedFlag(txn, c, singletonList(m));
transaction.attach(new MessagesSentEvent(c, singletonList(m),
message.getRawLength()));
}
return message;
}
@Override
public Collection<MessageId> getUnackedMessagesToSend(
Transaction transaction, ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
@@ -1069,6 +1080,20 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.removeTransportKeys(txn, t, k);
}
@Override
public void setAckSent(Transaction transaction, ContactId c,
Collection<MessageId> acked) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
List<MessageId> visible = new ArrayList<>(acked.size());
for (MessageId m : acked) {
if (db.containsVisibleMessage(txn, c, m)) visible.add(m);
}
db.lowerAckFlag(txn, c, visible);
}
@Override
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
long duration) throws DbException {
@@ -1115,7 +1140,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED);
List<ContactId> affected = Collections.singletonList(c);
List<ContactId> affected = singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}
@@ -1163,6 +1188,28 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new MessageStateChangedEvent(m, false, state));
}
@Override
public void setMessagesSent(Transaction transaction, ContactId c,
Collection<MessageId> sent, 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<MessageId> visible = new ArrayList<>(sent.size());
for (MessageId m : sent) {
if (db.containsVisibleMessage(txn, c, m)) {
visible.add(m);
totalLength += db.getMessageLength(txn, m);
db.updateRetransmissionData(txn, c, m, maxLatency);
}
}
db.lowerRequestedFlag(txn, c, visible);
if (!visible.isEmpty()) {
transaction.attach(new MessagesSentEvent(c, visible, totalLength));
}
}
@Override
public void addMessageDependencies(Transaction transaction,
Message dependent, Collection<MessageId> dependencies)

View File

@@ -2,8 +2,6 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.settings.Settings;
import static java.util.concurrent.TimeUnit.DAYS;
interface DatabaseConstants {
/**
@@ -25,19 +23,6 @@ interface DatabaseConstants {
*/
String SCHEMA_VERSION_KEY = "schemaVersion";
/**
* The {@link Settings} key under which the time of the last database
* compaction is stored.
*/
String LAST_COMPACTED_KEY = "lastCompacted";
/**
* The maximum time between database compactions in milliseconds. When the
* database is opened it will be compacted if more than this amount of time
* has passed since the last compaction.
*/
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
/**
* The {@link Settings} key under which the flag is stored indicating
* whether the database is marked as dirty.

View File

@@ -85,12 +85,17 @@ class H2Database extends JdbcDatabase {
public void close() throws DbException {
// H2 will close the database when the last connection closes
Connection c = null;
Statement s = null;
try {
c = createConnection();
super.closeAllConnections();
closeAllConnections();
setDirty(c, false);
s = c.createStatement();
s.execute("SHUTDOWN COMPACT");
s.close();
c.close();
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}

View File

@@ -79,11 +79,11 @@ class HyperSqlDatabase extends JdbcDatabase {
Connection c = null;
Statement s = null;
try {
super.closeAllConnections();
closeAllConnections();
c = createConnection();
setDirty(c, false);
s = c.createStatement();
s.executeQuery("SHUTDOWN");
s.executeQuery("SHUTDOWN COMPACT");
s.close();
c.close();
} catch (SQLException e) {
@@ -106,7 +106,7 @@ class HyperSqlDatabase extends JdbcDatabase {
Connection c = null;
Statement s = null;
try {
super.closeAllConnections();
closeAllConnections();
c = createConnection();
s = c.createStatement();
s.executeQuery("SHUTDOWN COMPACT");

View File

@@ -51,7 +51,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -70,12 +69,14 @@ import static java.sql.Types.BOOLEAN;
import static java.sql.Types.INTEGER;
import static java.sql.Types.VARCHAR;
import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
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.VISIBLE;
@@ -85,8 +86,6 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
@@ -102,7 +101,12 @@ import static org.briarproject.bramble.util.LogUtils.now;
abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing
static final int CODE_SCHEMA_VERSION = 49;
static final int CODE_SCHEMA_VERSION = 50;
/**
* The maximum number of idle connections to keep open.
*/
private static final int MAX_CONNECTION_POOL_SIZE = 1;
// Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1;
@@ -252,7 +256,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " requested BOOLEAN NOT NULL,"
+ " expiry BIGINT NOT NULL,"
+ " txCount INT NOT NULL,"
+ " eta BIGINT NOT NULL,"
+ " maxLatency BIGINT," // Null if latency was reset
+ " PRIMARY KEY (messageId, contactId),"
+ " FOREIGN KEY (messageId)"
+ " REFERENCES messages (messageId)"
@@ -365,7 +369,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private final Condition connectionsChanged = connectionsLock.newCondition();
@GuardedBy("connectionsLock")
private final LinkedList<Connection> connections = new LinkedList<>();
private final LinkedList<Connection> connectionPool = new LinkedList<>();
@GuardedBy("connectionsLock")
private int openConnections = 0;
@@ -378,8 +382,7 @@ abstract class JdbcDatabase implements Database<Connection> {
throws DbException, SQLException;
// Used exclusively during open to compact the database after schema
// migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
// elapsed
// migrations or if the database was not shut down cleanly
protected abstract void compactAndClose() throws DbException;
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
@@ -405,7 +408,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (reopen) {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
wasDirtyOnInitialisation = isDirty(s);
compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
boolean migrated = migrateSchema(txn, s, listener);
compact = wasDirtyOnInitialisation || migrated;
} else {
wasDirtyOnInitialisation = false;
createTables(txn);
@@ -435,14 +439,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} finally {
connectionsLock.unlock();
}
txn = startTransaction();
try {
storeLastCompacted(txn);
commitTransaction(txn);
} catch (DbException e) {
abortTransaction(txn);
throw e;
}
}
}
@@ -502,18 +498,11 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration45_46(),
new Migration46_47(dbTypes),
new Migration47_48(),
new Migration48_49()
new Migration48_49(),
new Migration49_50()
);
}
private boolean isCompactionDue(Settings s) {
long lastCompacted = s.getLong(LAST_COMPACTED_KEY, 0);
long elapsed = clock.currentTimeMillis() - lastCompacted;
if (LOG.isLoggable(INFO))
LOG.info(elapsed + " ms since last compaction");
return elapsed > MAX_COMPACTION_INTERVAL_MS;
}
private void storeSchemaVersion(Connection txn, int version)
throws DbException {
Settings s = new Settings();
@@ -521,12 +510,6 @@ abstract class JdbcDatabase implements Database<Connection> {
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private void storeLastCompacted(Connection txn) throws DbException {
Settings s = new Settings();
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private boolean isDirty(Settings s) {
return s.getBoolean(DIRTY_KEY, false);
}
@@ -540,7 +523,6 @@ abstract class JdbcDatabase implements Database<Connection> {
private void initialiseSettings(Connection txn) throws DbException {
Settings s = new Settings();
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
s.putLong(LAST_COMPACTED_KEY, clock.currentTimeMillis());
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
@@ -595,7 +577,8 @@ abstract class JdbcDatabase implements Database<Connection> {
connectionsLock.lock();
try {
if (closed) throw new DbClosedException();
txn = connections.poll();
txn = connectionPool.poll();
logConnectionCounts();
} finally {
connectionsLock.unlock();
}
@@ -606,7 +589,14 @@ abstract class JdbcDatabase implements Database<Connection> {
txn.setAutoCommit(false);
connectionsLock.lock();
try {
// The DB may have been closed since the check above
if (closed) {
tryToClose(txn, LOG, WARNING);
throw new DbClosedException();
}
openConnections++;
logConnectionCounts();
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
@@ -617,67 +607,91 @@ abstract class JdbcDatabase implements Database<Connection> {
return txn;
}
@Override
public void abortTransaction(Connection txn) {
try {
txn.rollback();
connectionsLock.lock();
try {
connections.add(txn);
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
} catch (SQLException e) {
// Try to close the connection
logException(LOG, WARNING, e);
tryToClose(txn, LOG, WARNING);
// Whatever happens, allow the database to close
connectionsLock.lock();
try {
openConnections--;
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
@GuardedBy("connectionsLock")
private void logConnectionCounts() {
if (LOG.isLoggable(FINE)) {
LOG.fine(openConnections + " connections open, "
+ connectionPool.size() + " in pool");
}
}
@Override
public void commitTransaction(Connection txn) throws DbException {
public void abortTransaction(Connection txn) {
// The transaction may have been aborted due to an earlier exception,
// so close the connection rather than returning it to the pool
try {
txn.commit();
txn.rollback();
} catch (SQLException e) {
throw new DbException(e);
logException(LOG, WARNING, e);
}
closeConnection(txn);
}
private void closeConnection(Connection txn) {
tryToClose(txn, LOG, WARNING);
connectionsLock.lock();
try {
connections.add(txn);
openConnections--;
logConnectionCounts();
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
}
void closeAllConnections() throws SQLException {
@Override
public void commitTransaction(Connection txn) throws DbException {
// If the transaction commits successfully then return the connection
// to the pool, otherwise close it
try {
txn.commit();
returnConnectionToPool(txn);
} catch (SQLException e) {
logException(LOG, WARNING, e);
closeConnection(txn);
throw new DbException(e);
}
}
private void returnConnectionToPool(Connection txn) {
boolean shouldClose;
connectionsLock.lock();
try {
shouldClose = connectionPool.size() >= MAX_CONNECTION_POOL_SIZE;
if (shouldClose) openConnections--;
else connectionPool.add(txn);
logConnectionCounts();
connectionsChanged.signalAll();
} finally {
connectionsLock.unlock();
}
if (shouldClose) tryToClose(txn, LOG, WARNING);
}
void closeAllConnections() {
boolean interrupted = false;
connectionsLock.lock();
try {
closed = true;
for (Connection c : connections) c.close();
openConnections -= connections.size();
connections.clear();
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
openConnections -= connectionPool.size();
connectionPool.clear();
while (openConnections > 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Waiting for " + openConnections
+ " connections to be closed");
}
try {
connectionsChanged.await();
} catch (InterruptedException e) {
LOG.warning("Interrupted while closing connections");
interrupted = true;
}
for (Connection c : connections) c.close();
openConnections -= connections.size();
connections.clear();
for (Connection c : connectionPool) tryToClose(c, LOG, WARNING);
openConnections -= connectionPool.size();
connectionPool.clear();
}
LOG.info("All connections closed");
} finally {
connectionsLock.unlock();
}
@@ -920,9 +934,10 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
+ " timestamp, length, state, groupShared, messageShared,"
+ " deleted, ack, seen, requested, expiry, txCount, eta)"
+ " deleted, ack, seen, requested, expiry, txCount,"
+ " maxLatency)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0,"
+ " 0)";
+ " NULL)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt());
@@ -1156,17 +1171,17 @@ abstract class JdbcDatabase implements Database<Connection> {
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 > ?)";
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
ps.setLong(4, maxLatency);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
@@ -1902,6 +1917,31 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public int getMessageLength(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT length from messages"
+ " WHERE messageId = ? AND state = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
int length = rs.getInt(1);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return length;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public Map<MessageId, Metadata> getMessageMetadata(Connection txn,
GroupId g) throws DbException {
@@ -2194,7 +2234,6 @@ abstract class JdbcDatabase implements Database<Connection> {
public Collection<MessageId> getMessagesToOffer(Connection txn,
ContactId c, int maxMessages, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
PreparedStatement ps = null;
ResultSet rs = null;
try {
@@ -2203,13 +2242,14 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE"
+ " AND seen = FALSE AND requested = FALSE"
+ " AND (expiry <= ? OR eta > ?)"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)"
+ " ORDER BY timestamp LIMIT ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
ps.setLong(4, maxLatency);
ps.setInt(5, maxMessages);
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>();
@@ -2250,10 +2290,9 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public Collection<MessageId> getMessagesToSend(Connection txn, ContactId c,
int maxLength, long maxLatency) throws DbException {
public Collection<MessageId> getMessagesToSend(Connection txn,
ContactId c, long capacity, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
PreparedStatement ps = null;
ResultSet rs = null;
try {
@@ -2262,21 +2301,21 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE"
+ " AND seen = FALSE"
+ " AND (expiry <= ? OR eta > ?)"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)"
+ " ORDER BY timestamp";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
ps.setLong(4, maxLatency);
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>();
int total = 0;
while (rs.next()) {
int length = rs.getInt(1);
if (total + length > maxLength) break;
if (capacity < RECORD_HEADER_BYTES + length) break;
ids.add(new MessageId(rs.getBytes(2)));
total += length;
capacity -= RECORD_HEADER_BYTES + length;
}
rs.close();
ps.close();
@@ -2289,12 +2328,12 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
public Collection<MessageId> getUnackedMessagesToSend(Connection txn,
ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT length, messageId FROM statuses"
String sql = "SELECT messageId FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
@@ -2303,15 +2342,11 @@ abstract class JdbcDatabase implements Database<Connection> {
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);
}
List<MessageId> ids = new ArrayList<>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close();
ps.close();
return results;
return ids;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
@@ -2424,6 +2459,7 @@ abstract class JdbcDatabase implements Database<Connection> {
MessageId m = new MessageId(rs.getBytes(1));
GroupId g = new GroupId(rs.getBytes(2));
Collection<MessageId> messageIds = ids.get(g);
//noinspection Java8MapApi
if (messageIds == null) {
messageIds = new ArrayList<>();
ids.put(g, messageIds);
@@ -2550,9 +2586,8 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override
public Collection<MessageId> getRequestedMessagesToSend(Connection txn,
ContactId c, int maxLength, long maxLatency) throws DbException {
ContactId c, long capacity, long maxLatency) throws DbException {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
PreparedStatement ps = null;
ResultSet rs = null;
try {
@@ -2561,21 +2596,21 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE"
+ " AND seen = FALSE AND requested = TRUE"
+ " AND (expiry <= ? OR eta > ?)"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)"
+ " ORDER BY timestamp";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
ps.setLong(4, maxLatency);
rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>();
int total = 0;
while (rs.next()) {
int length = rs.getInt(1);
if (total + length > maxLength) break;
if (capacity < RECORD_HEADER_BYTES + length) break;
ids.add(new MessageId(rs.getBytes(2)));
total += length;
capacity -= RECORD_HEADER_BYTES + length;
}
rs.close();
ps.close();
@@ -2729,6 +2764,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getString(2));
Collection<TransportId> transportIds = ids.get(c);
//noinspection Java8MapApi
if (transportIds == null) {
transportIds = new ArrayList<>();
ids.put(c, transportIds);
@@ -3298,7 +3334,8 @@ abstract class JdbcDatabase implements Database<Connection> {
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE statuses SET expiry = 0, txCount = 0, eta = 0"
String sql = "UPDATE statuses SET expiry = 0, txCount = 0,"
+ " maxLatency = NULL"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
@@ -3643,8 +3680,8 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
long maxLatency) throws DbException {
public void updateRetransmissionData(Connection txn, ContactId c,
MessageId m, long maxLatency) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
@@ -3660,13 +3697,12 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close();
ps.close();
sql = "UPDATE statuses"
+ " SET expiry = ?, txCount = txCount + 1, eta = ?"
+ " SET expiry = ?, txCount = txCount + 1, maxLatency = ?"
+ " WHERE messageId = ? AND contactId = ?";
ps = txn.prepareStatement(sql);
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
ps.setLong(1, calculateExpiry(now, maxLatency, txCount));
ps.setLong(2, eta);
ps.setLong(2, maxLatency);
ps.setBytes(3, m.getBytes());
ps.setInt(4, c.getInt());
int affected = ps.executeUpdate();

View File

@@ -0,0 +1,45 @@
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 Migration49_50 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration49_50.class.getName());
@Override
public int getStartVersion() {
return 49;
}
@Override
public int getEndVersion() {
return 50;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute("ALTER TABLE statuses"
+ " ALTER COLUMN eta"
+ " RENAME TO maxLatency");
s.execute("ALTER TABLE statuses"
+ " ALTER COLUMN maxLatency"
+ " SET NULL");
s.execute("UPDATE statuses SET maxLatency = NULL");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -1,10 +1,10 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful;
import java.io.IOException;

View File

@@ -0,0 +1,21 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
/**
* An interface for calling an API endpoint with the option to retry the call.
*/
interface ApiCall {
/**
* This method makes a synchronous call to an API endpoint and returns
* true if the call should be retried, in which case the method may be
* called again on the same {@link ApiCall} instance after a delay.
*
* @return True if the API call needs to be retried, or false if the API
* call succeeded or {@link TolerableFailureException failed tolerably}.
*/
@IoExecutor
boolean callApi();
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
/**
* An interface for checking whether a mailbox is reachable.
*/
@ThreadSafe
@NotNullByDefault
interface ConnectivityChecker {
/**
* Destroys the checker. Any current connectivity check is cancelled.
*/
void destroy();
/**
* Starts a connectivity check if needed and calls the given observer when
* the check succeeds. If a check is already running then the observer is
* called when the check succeeds. If a connectivity check has recently
* succeeded then the observer is called immediately.
*/
void checkConnectivity(MailboxProperties properties,
ConnectivityObserver o);
interface ConnectivityObserver {
void onConnectivityCheckSucceeded();
}
}

View File

@@ -0,0 +1,111 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
/**
* If no more than this much time has elapsed since the last connectivity
* check succeeded, consider the result to be fresh and don't check again.
* <p>
* Package access for testing.
*/
static final long CONNECTIVITY_CHECK_FRESHNESS_MS = 10_000;
private final Object lock = new Object();
protected final Clock clock;
private final MailboxApiCaller mailboxApiCaller;
@GuardedBy("lock")
private boolean destroyed = false;
@GuardedBy("lock")
@Nullable
private Cancellable connectivityCheck = null;
@GuardedBy("lock")
private long lastConnectivityCheckSucceeded = 0;
@GuardedBy("lock")
private final List<ConnectivityObserver> connectivityObservers =
new ArrayList<>();
/**
* Creates an {@link ApiCall} for checking whether the mailbox is
* reachable. The {@link ApiCall} should call
* {@link #onConnectivityCheckSucceeded(long)} if the check succeeds.
*/
abstract ApiCall createConnectivityCheckTask(MailboxProperties properties);
ConnectivityCheckerImpl(Clock clock, MailboxApiCaller mailboxApiCaller) {
this.clock = clock;
this.mailboxApiCaller = mailboxApiCaller;
}
@Override
public void destroy() {
synchronized (lock) {
destroyed = true;
connectivityObservers.clear();
if (connectivityCheck != null) {
connectivityCheck.cancel();
connectivityCheck = null;
}
}
}
@Override
public void checkConnectivity(MailboxProperties properties,
ConnectivityObserver o) {
boolean callNow = false;
synchronized (lock) {
if (destroyed) return;
if (connectivityCheck == null) {
// No connectivity check is running
long now = clock.currentTimeMillis();
if (now - lastConnectivityCheckSucceeded
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
// The last connectivity check is stale, start a new one
connectivityObservers.add(o);
ApiCall task =
createConnectivityCheckTask(properties);
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
} else {
// The last connectivity check is fresh
callNow = true;
}
} else {
// A connectivity check is running, wait for it to succeed
connectivityObservers.add(o);
}
}
if (callNow) o.onConnectivityCheckSucceeded();
}
protected void onConnectivityCheckSucceeded(long now) {
List<ConnectivityObserver> observers;
synchronized (lock) {
if (destroyed) return;
connectivityCheck = null;
lastConnectivityCheckSucceeded = now;
observers = new ArrayList<>(connectivityObservers);
connectivityObservers.clear();
}
for (ConnectivityObserver o : observers) {
o.onConnectivityCheckSucceeded();
}
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private final MailboxApi mailboxApi;
ContactMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
super(clock, mailboxApiCaller);
this.mailboxApi = mailboxApi;
}
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (properties.isOwner()) throw new IllegalArgumentException();
return new SimpleApiCall() {
@Override
void tryToCallApi() throws IOException, ApiException {
if (!mailboxApi.checkStatus(properties)) {
throw new ApiException();
}
// Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis());
}
};
}
}

View File

@@ -7,6 +7,9 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
@@ -16,8 +19,21 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.singletonList;
@NotNullByDefault
interface MailboxApi {
/**
* Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}.
*/
List<MailboxVersion> CLIENT_SUPPORTS = singletonList(
new MailboxVersion(1, 0));
List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException;
/**
* Sets up the mailbox with the setup token.
*
@@ -25,7 +41,7 @@ interface MailboxApi {
* @return the owner token
* @throws ApiException for 401 response.
*/
MailboxAuthToken setup(MailboxProperties properties)
MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException;
/**

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
@NotNullByDefault
interface MailboxApiCaller {
/**
* The minimum interval between retries in milliseconds.
*/
long MIN_RETRY_INTERVAL_MS = MINUTES.toMillis(1);
/**
* The maximum interval between retries in milliseconds.
*/
long MAX_RETRY_INTERVAL_MS = DAYS.toMillis(1);
/**
* Asynchronously calls the given API call on the {@link IoExecutor},
* automatically retrying at increasing intervals until the API call
* returns false or retries are cancelled.
*
* @return A {@link Cancellable} that can be used to cancel any future
* retries.
*/
Cancellable retryWithBackoff(ApiCall apiCall);
}

View File

@@ -0,0 +1,98 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@Immutable
@NotNullByDefault
class MailboxApiCallerImpl implements MailboxApiCaller {
private final TaskScheduler taskScheduler;
private final Executor ioExecutor;
@Inject
MailboxApiCallerImpl(TaskScheduler taskScheduler,
@IoExecutor Executor ioExecutor) {
this.taskScheduler = taskScheduler;
this.ioExecutor = ioExecutor;
}
@Override
public Cancellable retryWithBackoff(ApiCall apiCall) {
Task task = new Task(apiCall);
task.start();
return task;
}
private class Task implements Cancellable {
private final ApiCall apiCall;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private Cancellable scheduledTask = null;
@GuardedBy("lock")
private boolean cancelled = false;
@GuardedBy("lock")
private long retryIntervalMs = MIN_RETRY_INTERVAL_MS;
private Task(ApiCall apiCall) {
this.apiCall = apiCall;
}
private void start() {
synchronized (lock) {
if (cancelled) throw new AssertionError();
ioExecutor.execute(this::callApi);
}
}
@IoExecutor
private void callApi() {
synchronized (lock) {
if (cancelled) return;
}
// The call returns true if we should retry
if (apiCall.callApi()) {
synchronized (lock) {
if (cancelled) return;
scheduledTask = taskScheduler.schedule(this::callApi,
ioExecutor, retryIntervalMs, MILLISECONDS);
// Increase the retry interval each time we retry
retryIntervalMs =
min(MAX_RETRY_INTERVAL_MS, retryIntervalMs * 2);
}
} else {
synchronized (lock) {
scheduledTask = null;
}
}
}
@Override
public void cancel() {
Cancellable scheduledTask;
synchronized (lock) {
cancelled = true;
scheduledTask = this.scheduledTask;
this.scheduledTask = null;
}
if (scheduledTask != null) scheduledTask.cancel();
}
}
}

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
@@ -56,7 +57,24 @@ class MailboxApiImpl implements MailboxApi {
}
@Override
public MailboxAuthToken setup(MailboxProperties properties)
public List<MailboxVersion> getServerSupports(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/versions");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
return parseServerSupports(node);
} catch (JacksonException e) {
throw new ApiException();
}
}
@Override
public MailboxProperties setup(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
@@ -75,17 +93,40 @@ class MailboxApiImpl implements MailboxApi {
if (tokenNode == null) {
throw new ApiException();
}
String ownerToken = tokenNode.textValue();
return MailboxAuthToken.fromString(ownerToken);
return new MailboxProperties(properties.getBaseUrl(),
MailboxAuthToken.fromString(tokenNode.textValue()),
parseServerSupports(node));
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
private List<MailboxVersion> parseServerSupports(JsonNode node)
throws ApiException {
List<MailboxVersion> serverSupports = new ArrayList<>();
ArrayNode serverSupportsNode = getArray(node, "serverSupports");
for (JsonNode versionNode : serverSupportsNode) {
if (!versionNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) versionNode;
JsonNode majorNode = objectNode.get("major");
JsonNode minorNode = objectNode.get("minor");
if (majorNode == null || !majorNode.isNumber()) {
throw new ApiException();
}
if (minorNode == null || !minorNode.isNumber()) {
throw new ApiException();
}
int major = majorNode.asInt();
int minor = minorNode.asInt();
if (major < 0 || minor < 0) throw new ApiException();
serverSupports.add(new MailboxVersion(major, minor));
}
return serverSupports;
}
@Override
public boolean checkStatus(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/status");
if (response.code() == 401) throw new ApiException();
return response.isSuccessful();

View File

@@ -2,27 +2,42 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@NotNullByDefault
class MailboxManagerImpl implements MailboxManager {
private static final String TAG = MailboxManagerImpl.class.getName();
private final static Logger LOG = getLogger(TAG);
private final Executor ioExecutor;
private final MailboxApi api;
private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxPairingTaskFactory pairingTaskFactory;
private final Clock clock;
private final Object lock = new Object();
@Nullable
@@ -32,11 +47,17 @@ class MailboxManagerImpl implements MailboxManager {
@Inject
MailboxManagerImpl(
@IoExecutor Executor ioExecutor,
MailboxApi api,
TransactionManager db,
MailboxSettingsManager mailboxSettingsManager,
MailboxPairingTaskFactory pairingTaskFactory) {
MailboxPairingTaskFactory pairingTaskFactory,
Clock clock) {
this.ioExecutor = ioExecutor;
this.api = api;
this.db = db;
this.mailboxSettingsManager = mailboxSettingsManager;
this.pairingTaskFactory = pairingTaskFactory;
this.clock = clock;
}
@Override
@@ -75,4 +96,61 @@ class MailboxManagerImpl implements MailboxManager {
return created;
}
@Override
public boolean checkConnection() {
boolean success;
try {
MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (props == null) throw new DbException();
success = api.checkStatus(props);
} catch (DbException e) {
logException(LOG, WARNING, e);
// we don't treat this is a failure to record
return false;
} catch (IOException | MailboxApi.ApiException e) {
// we record this as a failure
success = false;
logException(LOG, WARNING, e);
}
try {
recordCheckResult(success);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
return success;
}
private void recordCheckResult(boolean success) throws DbException {
long now = clock.currentTimeMillis();
db.transaction(false, txn -> {
if (success) {
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
} else {
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
}
});
}
@Override
public boolean unPair() throws DbException {
MailboxProperties properties = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (properties == null) {
// no more mailbox, that's strange but possible if called in quick
// succession, so let's return true this time
return true;
}
boolean wasWiped;
try {
api.wipeMailbox(properties);
wasWiped = true;
} catch (IOException | MailboxApi.ApiException e) {
logException(LOG, WARNING, e);
wasWiped = false;
}
db.transaction(false,
mailboxSettingsManager::removeOwnMailboxProperties);
return wasWiped;
}
}

View File

@@ -1,16 +1,41 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FeatureFlags;
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.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.CLIENT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MINOR_VERSION;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
@Module
public class MailboxModule {
public static class EagerSingletons {
@Inject
MailboxUpdateValidator mailboxUpdateValidator;
@Inject
MailboxUpdateManager mailboxUpdateManager;
}
@Provides
@Singleton
MailboxManager providesMailboxManager(MailboxManagerImpl mailboxManager) {
@@ -33,4 +58,47 @@ public class MailboxModule {
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
return mailboxApi;
}
@Provides
@Singleton
MailboxUpdateValidator provideMailboxUpdateValidator(
ValidationManager validationManager,
ClientHelper clientHelper,
MetadataEncoder metadataEncoder,
Clock clock,
FeatureFlags featureFlags) {
MailboxUpdateValidator validator = new MailboxUpdateValidator(
clientHelper, metadataEncoder, clock);
if (featureFlags.shouldEnableMailbox()) {
validationManager.registerMessageValidator(CLIENT_ID,
MAJOR_VERSION, validator);
}
return validator;
}
@Provides
List<MailboxVersion> provideClientSupports() {
return CLIENT_SUPPORTS;
}
@Provides
@Singleton
MailboxUpdateManager provideMailboxUpdateManager(
FeatureFlags featureFlags,
LifecycleManager lifecycleManager,
ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager,
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManagerImpl mailboxUpdateManager) {
if (featureFlags.shouldEnableMailbox()) {
lifecycleManager.registerOpenDatabaseHook(mailboxUpdateManager);
validationManager.registerIncomingMessageHook(CLIENT_ID,
MAJOR_VERSION, mailboxUpdateManager);
contactManager.registerContactHook(mailboxUpdateManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, mailboxUpdateManager);
mailboxSettingsManager.registerMailboxHook(mailboxUpdateManager);
}
return mailboxUpdateManager;
}
}

View File

@@ -1,10 +1,11 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
@@ -18,31 +19,35 @@ import javax.inject.Inject;
class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final Executor eventExecutor;
private final TransactionManager db;
private final DatabaseComponent db;
private final CryptoComponent crypto;
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
@Inject
MailboxPairingTaskFactoryImpl(
@EventExecutor Executor eventExecutor,
TransactionManager db,
DatabaseComponent db,
CryptoComponent crypto,
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager) {
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager;
}
@Override
public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager);
crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager);
}
}

View File

@@ -2,15 +2,18 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
@@ -43,11 +46,12 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final String payload;
private final Executor eventExecutor;
private final TransactionManager db;
private final DatabaseComponent db;
private final CryptoComponent crypto;
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager;
private final Object lock = new Object();
@GuardedBy("lock")
@@ -59,11 +63,12 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
MailboxPairingTaskImpl(
String payload,
@EventExecutor Executor eventExecutor,
TransactionManager db,
DatabaseComponent db,
CryptoComponent crypto,
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager) {
MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) {
this.payload = payload;
this.eventExecutor = eventExecutor;
this.db = db;
@@ -71,7 +76,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
state = new MailboxPairingState.QrCodeReceived(payload);
this.mailboxUpdateManager = mailboxUpdateManager;
state = new MailboxPairingState.QrCodeReceived();
}
@Override
@@ -100,23 +106,31 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
} catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
} catch (IOException e) {
onMailboxError(e, new MailboxPairingState.ConnectionError(payload));
onMailboxError(e, new MailboxPairingState.ConnectionError());
} catch (ApiException | DbException e) {
onMailboxError(e, new MailboxPairingState.UnexpectedError(payload));
onMailboxError(e, new MailboxPairingState.UnexpectedError());
}
}
private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing(payload));
MailboxAuthToken ownerToken = api.setup(mailboxProperties);
MailboxProperties ownerProperties = new MailboxProperties(
mailboxProperties.getBaseUrl(), ownerToken, true);
setState(new MailboxPairingState.Pairing());
MailboxProperties ownerProperties = api.setup(mailboxProperties);
long time = clock.currentTimeMillis();
db.transaction(false, txn -> {
mailboxSettingsManager
.setOwnMailboxProperties(txn, ownerProperties);
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
// A (possibly new) mailbox is paired. Reset message retransmission
// timers for contacts who doesn't have their own mailbox. This way,
// data stranded on our old mailbox will be re-uploaded to our new.
for (Contact c : db.getContacts(txn)) {
MailboxUpdate update = mailboxUpdateManager.getRemoteUpdate(
txn, c.getId());
if (update == null || !update.hasMailbox()) {
db.resetUnackedMessagesToSend(txn, c.getId());
}
}
});
setState(new MailboxPairingState.Paired());
}
@@ -162,11 +176,11 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
}
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onionAddress = crypto.encodeOnionAddress(onionPubKey);
String baseUrl = "http://" + onionAddress + ".onion";
String onion = crypto.encodeOnion(onionPubKey);
String baseUrl = "http://" + onion + ".onion"; // TODO
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(baseUrl, setupToken, true);
return new MailboxProperties(baseUrl, setupToken, new ArrayList<>());
}
}

View File

@@ -8,10 +8,17 @@ import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.MailboxProblemEvent;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@@ -24,20 +31,28 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
// Package access for testing
static final String SETTINGS_NAMESPACE = "mailbox";
// TODO: This currently stores the base URL, not the 56-char onion address
static final String SETTINGS_KEY_ONION = "onion";
static final String SETTINGS_KEY_TOKEN = "token";
static final String SETTINGS_KEY_SERVER_SUPPORTS = "serverSupports";
static final String SETTINGS_KEY_LAST_ATTEMPT = "lastAttempt";
static final String SETTINGS_KEY_LAST_SUCCESS = "lastSuccess";
static final String SETTINGS_KEY_ATTEMPTS = "attempts";
static final String SETTINGS_UPLOADS_NAMESPACE = "mailbox-uploads";
private final SettingsManager settingsManager;
private final List<MailboxHook> hooks = new CopyOnWriteArrayList<>();
@Inject
MailboxSettingsManagerImpl(SettingsManager settingsManager) {
this.settingsManager = settingsManager;
}
@Override
public void registerMailboxHook(MailboxHook hook) {
hooks.add(hook);
}
@Override
public MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException {
@@ -45,9 +60,18 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
String onion = s.get(SETTINGS_KEY_ONION);
String token = s.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
int[] ints = s.getIntArray(SETTINGS_KEY_SERVER_SUPPORTS);
// We know we were paired, so we must have proper serverSupports
if (ints == null || ints.length == 0 || ints.length % 2 != 0) {
throw new DbException();
}
List<MailboxVersion> serverSupports = new ArrayList<>();
for (int i = 0; i < ints.length - 1; i += 2) {
serverSupports.add(new MailboxVersion(ints[i], ints[i + 1]));
}
try {
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
return new MailboxProperties(onion, tokenId, true);
return new MailboxProperties(onion, tokenId, serverSupports);
} catch (InvalidMailboxIdException e) {
throw new DbException(e);
}
@@ -59,7 +83,29 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
List<MailboxVersion> serverSupports = p.getServerSupports();
int[] ints = new int[serverSupports.size() * 2];
int i = 0;
for (MailboxVersion v : serverSupports) {
ints[i++] = v.getMajor();
ints[i++] = v.getMinor();
}
s.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, ints);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
}
}
@Override
public void removeOwnMailboxProperties(Transaction txn) throws DbException {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, "");
s.put(SETTINGS_KEY_TOKEN, "");
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
for (MailboxHook hook : hooks) {
hook.mailboxUnpaired(txn);
}
}
@Override
@@ -80,6 +126,8 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
MailboxStatus status = new MailboxStatus(now, now, 0);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
}
@Override
@@ -87,11 +135,15 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
int attempts = oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
int newAttempts = 1 + oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
long lastSuccess = oldSettings.getLong(SETTINGS_KEY_LAST_SUCCESS, 0);
Settings newSettings = new Settings();
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts + 1);
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, newAttempts);
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
MailboxStatus status = new MailboxStatus(now, lastSuccess, newAttempts);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
if (status.hasProblem(now)) txn.attach(new MailboxProblemEvent());
}
@Override

View File

@@ -0,0 +1,373 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.FormatException;
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.CryptoComponent;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.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.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager.MailboxHook;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.RemoteMailboxUpdateEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
@NotNullByDefault
class MailboxUpdateManagerImpl implements MailboxUpdateManager,
OpenDatabaseHook, ContactHook, ClientVersioningHook,
IncomingMessageHook, MailboxHook {
private final List<MailboxVersion> clientSupports;
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final ClientVersioningManager clientVersioningManager;
private final MetadataParser metadataParser;
private final ContactGroupFactory contactGroupFactory;
private final Clock clock;
private final MailboxSettingsManager mailboxSettingsManager;
private final CryptoComponent crypto;
private final Group localGroup;
@Inject
MailboxUpdateManagerImpl(List<MailboxVersion> clientSupports,
DatabaseComponent db, ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory, Clock clock,
MailboxSettingsManager mailboxSettingsManager,
CryptoComponent crypto) {
this.clientSupports = clientSupports;
this.db = db;
this.clientHelper = clientHelper;
this.clientVersioningManager = clientVersioningManager;
this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory;
this.clock = clock;
this.mailboxSettingsManager = mailboxSettingsManager;
this.crypto = crypto;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
MAJOR_VERSION);
}
@Override
public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) {
try {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(
txn, localGroup.getId());
BdfList sent = meta.getList(GROUP_KEY_SENT_CLIENT_SUPPORTS);
if (clientHelper.parseMailboxVersionList(sent)
.equals(clientSupports)) {
return;
}
} catch (FormatException e) {
throw new DbException();
}
// Our current clientSupports list has changed compared to what we
// last sent out.
for (Contact c : db.getContacts(txn)) {
MailboxUpdate latest = getLocalUpdate(txn, c.getId());
MailboxUpdate updated;
if (latest.hasMailbox()) {
updated = new MailboxUpdateWithMailbox(
(MailboxUpdateWithMailbox) latest, clientSupports);
} else {
updated = new MailboxUpdate(clientSupports);
}
Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), updated);
}
} else {
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) {
addingContact(txn, c);
}
}
try {
BdfDictionary meta = BdfDictionary.of(new BdfEntry(
GROUP_KEY_SENT_CLIENT_SUPPORTS,
encodeSupportsList(clientSupports)));
clientHelper.mergeGroupMetadata(txn, localGroup.getId(), meta);
} catch (FormatException e) {
throw new DbException();
}
}
@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);
// 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);
// Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId());
MailboxProperties ownProps =
mailboxSettingsManager.getOwnMailboxProperties(txn);
if (ownProps != null) {
// We are paired, create and send props to the newly added contact
createAndSendUpdateWithMailbox(txn, c, ownProps.getServerSupports(),
ownProps.getOnion());
} else {
// Not paired, but we still want to get our clientSupports sent
sendUpdateNoMailbox(txn, c);
}
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c));
}
@Override
public void mailboxPaired(Transaction txn, String ownOnion,
List<MailboxVersion> serverSupports) throws DbException {
for (Contact c : db.getContacts(txn)) {
createAndSendUpdateWithMailbox(txn, c, serverSupports, ownOnion);
}
}
@Override
public void mailboxUnpaired(Transaction txn) throws DbException {
for (Contact c : db.getContacts(txn)) {
sendUpdateNoMailbox(txn, 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
public DeliveryAction incomingMessage(Transaction txn, Message m,
Metadata meta) throws DbException, InvalidMessageException {
try {
BdfDictionary d = metadataParser.parse(meta);
// Get latest non-local update in the same group (from same contact)
LatestUpdate latest = findLatest(txn, m.getGroupId(), false);
if (latest != null) {
if (d.getLong(MSG_KEY_VERSION) > latest.version) {
db.deleteMessage(txn, latest.messageId);
db.deleteMessageMetadata(txn, latest.messageId);
} else {
// Delete this update, we already have a newer one
db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId());
return ACCEPT_DO_NOT_SHARE;
}
}
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
BdfList body = clientHelper.getMessageAsList(txn, m.getId());
MailboxUpdate u = parseUpdate(body);
txn.attach(new RemoteMailboxUpdateEvent(c, u));
// Reset message retransmission timers for the contact. Avoiding
// messages getting stranded:
// - on our mailbox, if they now have a mailbox but didn't before
// - on the contact's old mailbox, if they removed their mailbox
// - on the contact's old mailbox, if they replaced their mailbox
db.resetUnackedMessagesToSend(txn, c);
} catch (FormatException e) {
throw new InvalidMessageException(e);
}
return ACCEPT_DO_NOT_SHARE;
}
@Override
public MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException {
MailboxUpdate local = getUpdate(txn, db.getContact(txn, c), true);
// An update (with or without mailbox) is created when contact is added
if (local == null) {
throw new DbException();
}
return local;
}
@Override
@Nullable
public MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException {
return getUpdate(txn, db.getContact(txn, c), false);
}
/**
* Creates and sends an update message to the given contact. The message
* holds our own mailbox's onion, generated unique properties, and lists of
* supported Mailbox API version(s). All of which the contact needs to
* communicate with our Mailbox.
*/
private void createAndSendUpdateWithMailbox(Transaction txn, Contact c,
List<MailboxVersion> serverSupports, String ownOnion)
throws DbException {
String baseUrl = "http://" + ownOnion + ".onion"; // TODO
MailboxProperties properties = new MailboxProperties(baseUrl,
new MailboxAuthToken(crypto.generateUniqueId().getBytes()),
serverSupports,
new MailboxFolderId(crypto.generateUniqueId().getBytes()),
new MailboxFolderId(crypto.generateUniqueId().getBytes()));
MailboxUpdate u =
new MailboxUpdateWithMailbox(clientSupports, properties);
Group g = getContactGroup(c);
storeMessageReplaceLatest(txn, g.getId(), u);
}
/**
* Sends an update message with empty properties to the given contact. The
* empty update indicates for the receiving contact that we don't have any
* Mailbox that they can use. It still includes the list of Mailbox API
* version(s) that we support as a client.
*/
private void sendUpdateNoMailbox(Transaction txn, Contact c)
throws DbException {
Group g = getContactGroup(c);
MailboxUpdate u = new MailboxUpdate(clientSupports);
storeMessageReplaceLatest(txn, g.getId(), u);
}
@Nullable
private MailboxUpdate getUpdate(Transaction txn, Contact c, boolean local)
throws DbException {
MailboxUpdate u = null;
Group g = getContactGroup(c);
try {
LatestUpdate latest = findLatest(txn, g.getId(), local);
if (latest != null) {
BdfList body =
clientHelper.getMessageAsList(txn, latest.messageId);
u = parseUpdate(body);
}
} catch (FormatException e) {
throw new DbException(e);
}
return u;
}
private void storeMessageReplaceLatest(Transaction txn, GroupId g,
MailboxUpdate u) throws DbException {
try {
LatestUpdate latest = findLatest(txn, g, true);
long version = latest == null ? 1 : latest.version + 1;
Message m = clientHelper.createMessage(g, clock.currentTimeMillis(),
encodeProperties(version, u));
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
if (latest != null) {
db.removeMessage(txn, latest.messageId);
}
} catch (FormatException e) {
throw new DbException(e);
}
}
@Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
throws DbException, FormatException {
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
// We should have at most 1 local and 1 remote
if (metadata.size() > 2) {
throw new IllegalStateException();
}
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getBoolean(MSG_KEY_LOCAL) == local) {
return new LatestUpdate(e.getKey(),
meta.getLong(MSG_KEY_VERSION));
}
}
return null;
}
private BdfList encodeProperties(long version, MailboxUpdate u) {
BdfDictionary dict = new BdfDictionary();
BdfList serverSupports = new BdfList();
if (u.hasMailbox()) {
MailboxUpdateWithMailbox um = (MailboxUpdateWithMailbox) u;
MailboxProperties properties = um.getMailboxProperties();
serverSupports = encodeSupportsList(properties.getServerSupports());
dict.put(PROP_KEY_ONION, properties.getOnion());
dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken());
dict.put(PROP_KEY_INBOXID, properties.getInboxId());
dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId());
}
return BdfList.of(version, encodeSupportsList(u.getClientSupports()),
serverSupports, dict);
}
private BdfList encodeSupportsList(List<MailboxVersion> supportsList) {
BdfList supports = new BdfList();
for (MailboxVersion version : supportsList) {
supports.add(BdfList.of(version.getMajor(), version.getMinor()));
}
return supports;
}
private MailboxUpdate parseUpdate(BdfList body) throws FormatException {
BdfList clientSupports = body.getList(1);
BdfList serverSupports = body.getList(2);
BdfDictionary dict = body.getDictionary(3);
return clientHelper.parseAndValidateMailboxUpdate(clientSupports,
serverSupports, dict);
}
private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID, MAJOR_VERSION,
c);
}
private static class LatestUpdate {
private final MessageId messageId;
private final long version;
private LatestUpdate(MessageId messageId, long version) {
this.messageId = messageId;
this.version = version;
}
}
}

View File

@@ -0,0 +1,54 @@
package org.briarproject.bramble.mailbox;
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.sync.Group;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.MSG_KEY_VERSION;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable
@NotNullByDefault
class MailboxUpdateValidator extends BdfMessageValidator {
MailboxUpdateValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws InvalidMessageException, FormatException {
// Version, Properties, clientSupports, serverSupports
checkSize(body, 4);
// Version
long version = body.getLong(0);
if (version < 0) throw new FormatException();
// clientSupports
BdfList clientSupports = body.getList(1);
// serverSupports
BdfList serverSupports = body.getList(2);
// Properties
BdfDictionary dictionary = body.getDictionary(3);
clientHelper.parseAndValidateMailboxUpdate(clientSupports,
serverSupports, dictionary);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, false);
return new BdfMessageContext(meta);
}
}

View File

@@ -0,0 +1,75 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private static final Logger LOG =
getLogger(OwnMailboxConnectivityChecker.class.getName());
private final MailboxApi mailboxApi;
private final TransactionManager db;
private final MailboxSettingsManager mailboxSettingsManager;
OwnMailboxConnectivityChecker(Clock clock,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
TransactionManager db,
MailboxSettingsManager mailboxSettingsManager) {
super(clock, mailboxApiCaller);
this.mailboxApi = mailboxApi;
this.db = db;
this.mailboxSettingsManager = mailboxSettingsManager;
}
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (!properties.isOwner()) throw new IllegalArgumentException();
return () -> {
try {
return checkConnectivityAndStoreResult(properties);
} catch (DbException e) {
logException(LOG, WARNING, e);
return true; // Retry
}
};
}
private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException {
try {
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
LOG.info("Own mailbox is reachable");
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordSuccessfulConnection(txn, now));
// Call the observers and cache the result
onConnectivityCheckSucceeded(now);
return false; // Don't retry
} catch (IOException | ApiException e) {
LOG.warning("Own mailbox is unreachable");
logException(LOG, WARNING, e);
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordFailedConnectionAttempt(txn, now));
}
return true; // Retry
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
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.util.LogUtils.logException;
/**
* Convenience class for making simple API calls that don't return values.
*/
@NotNullByDefault
public abstract class SimpleApiCall implements ApiCall {
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
abstract void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
@Override
public boolean callApi() {
try {
tryToCallApi();
return false; // Succeeded, don't retry
} catch (IOException | ApiException e) {
logException(LOG, WARNING, e);
return true; // Failed, retry with backoff
} catch (TolerableFailureException e) {
logException(LOG, INFO, e);
return false; // Failed tolerably, don't retry
}
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
@@ -27,7 +28,6 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful;
import org.briarproject.bramble.api.system.WakefulIoExecutor;

View File

@@ -11,6 +11,7 @@ public interface CircumventionProvider {
enum BridgeType {
DEFAULT_OBFS4,
NON_DEFAULT_OBFS4,
VANILLA,
MEEK
}
@@ -20,30 +21,30 @@ public interface CircumventionProvider {
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
String[] BLOCKED = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where obfs4 or meek bridge connections are likely to work.
* Countries where bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_OBFS4_BRIDGES}, {@link #NON_DEFAULT_OBFS4_BRIDGES} and
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #MEEK_BRIDGES}.
*/
String[] BRIDGES = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
/**
* Countries where default obfs4 bridges are likely to work.
* Countries where default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] DEFAULT_OBFS4_BRIDGES = {"EG", "BY", "TR", "SY", "VE"};
String[] DEFAULT_BRIDGES = {"EG", "VE"};
/**
* Countries where non-default obfs4 bridges are likely to work.
* Countries where non-default obfs4 or vanilla bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NON_DEFAULT_OBFS4_BRIDGES = {"RU"};
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
/**
* Countries where obfs4 bridges won't work and meek is needed.
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
* Should be a subset of {@link #BRIDGES}.
*/
String[] MEEK_BRIDGES = {"CN", "IR"};
@@ -60,10 +61,11 @@ public interface CircumventionProvider {
boolean doBridgesWork(String countryCode);
/**
* Returns the best type of bridge connection for the given country, or
* {@link #DEFAULT_OBFS4_BRIDGES} if no bridge type is known to work.
* Returns the types of bridge connection that are suitable for the given
* country, or {@link #DEFAULT_BRIDGES} if no bridge type is known
* to work.
*/
BridgeType getBestBridgeType(String countryCode);
List<BridgeType> getSuitableBridgeTypes(String countryCode);
@IoExecutor
List<String> getBridges(BridgeType type);

View File

@@ -14,10 +14,12 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
@Immutable
@NotNullByDefault
@@ -30,9 +32,9 @@ class CircumventionProviderImpl implements CircumventionProvider {
private static final Set<String> BRIDGE_COUNTRIES =
new HashSet<>(asList(BRIDGES));
private static final Set<String> DEFAULT_OBFS4_BRIDGE_COUNTRIES =
new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES));
private static final Set<String> NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES));
new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> MEEK_COUNTRIES =
new HashSet<>(asList(MEEK_BRIDGES));
@@ -51,15 +53,15 @@ class CircumventionProviderImpl implements CircumventionProvider {
}
@Override
public BridgeType getBestBridgeType(String countryCode) {
public List<BridgeType> getSuitableBridgeTypes(String countryCode) {
if (DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
return DEFAULT_OBFS4;
} else if (NON_DEFAULT_OBFS4_BRIDGE_COUNTRIES.contains(countryCode)) {
return NON_DEFAULT_OBFS4;
return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (MEEK_COUNTRIES.contains(countryCode)) {
return MEEK;
return singletonList(MEEK);
} else {
return DEFAULT_OBFS4;
return asList(DEFAULT_OBFS4, VANILLA);
}
}
@@ -73,12 +75,10 @@ class CircumventionProviderImpl implements CircumventionProvider {
List<String> bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
boolean isDefaultObfs4 = line.startsWith("d ");
boolean isNonDefaultObfs4 = line.startsWith("n ");
boolean isMeek = line.startsWith("m ");
if ((type == DEFAULT_OBFS4 && isDefaultObfs4) ||
(type == NON_DEFAULT_OBFS4 && isNonDefaultObfs4) ||
(type == MEEK && isMeek)) {
if ((type == DEFAULT_OBFS4 && line.startsWith("d ")) ||
(type == NON_DEFAULT_OBFS4 && line.startsWith("n ")) ||
(type == VANILLA && line.startsWith("v ")) ||
(type == MEEK && line.startsWith("m "))) {
bridges.add(line.substring(2));
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.tor;
import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection;
import net.freehaven.tor.control.TorNotRunningException;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair;
@@ -85,7 +86,6 @@ import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
@@ -93,7 +93,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -108,7 +110,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = {
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
"CIRC",
"ORCONN",
"STATUS_GENERAL",
"STATUS_CLIENT",
"HS_DESC",
"NOTICE",
"WARN",
"ERR"
};
private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000;
@@ -228,7 +237,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
// Load the settings
settings = migrateSettings(callback.getSettings());
settings = callback.getSettings();
// Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets();
if (cookieFile.exists() && !cookieFile.delete())
@@ -302,11 +311,19 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS));
// Check whether Tor has already bootstrapped
String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) {
String info = controlConnection.getInfo("status/bootstrap-phase");
if (info != null && info.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
state.setBootstrapped();
}
// Check whether Tor has already built a circuit
info = controlConnection.getInfo("status/circuit-established");
if ("1".equals(info)) {
LOG.info("Tor has already built a circuit");
state.getAndSetCircuitBuilt(true);
}
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new PluginException(e);
}
@@ -318,18 +335,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind();
}
// TODO: Remove after a reasonable migration period (added 2020-06-25)
private Settings migrateSettings(Settings settings) {
int network = settings.getInt(PREF_TOR_NETWORK,
DEFAULT_PREF_TOR_NETWORK);
if (network == PREF_TOR_NETWORK_NEVER) {
settings.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
settings.putBoolean(PREF_PLUGIN_ENABLE, false);
callback.mergeSettings(settings);
}
return settings;
}
private boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime();
}
@@ -339,9 +344,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
// The GeoIP file may exist from a previous installation - we can
// save some space by deleting it.
// TODO: Remove after a reasonable migration period
// (added 2022-03-29)
//noinspection ResultOfMethodCallIgnored
geoIpFile.delete();
installTorExecutable();
installObfs4Executable();
extract(getGeoIpInputStream(), geoIpFile);
extract(getConfigInputStream(), configFile);
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
@@ -379,14 +389,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return zin;
}
private InputStream getGeoIpInputStream() throws IOException {
InputStream in = resourceProvider.getResourceInputStream("geoip",
".zip");
ZipInputStream zin = new ZipInputStream(in);
if (zin.getNextEntry() == null) throw new IOException();
return zin;
}
private InputStream getObfs4InputStream() throws IOException {
InputStream in = resourceProvider
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
@@ -493,6 +495,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} else {
response = controlConnection.addOnion(privKey, portLines);
}
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
@@ -541,26 +545,38 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
protected void enableNetwork(boolean enable) throws IOException {
state.enableNetwork(enable);
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
try {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
}
private void enableBridges(boolean enable, BridgeType bridgeType)
private void enableBridges(boolean enable, List<BridgeType> bridgeTypes)
throws IOException {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (bridgeType == MEEK) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
try {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (bridgeTypes.contains(MEEK)) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
}
if (bridgeTypes.contains(DEFAULT_OBFS4) ||
bridgeTypes.contains(NON_DEFAULT_OBFS4)) {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
for (BridgeType bridgeType : bridgeTypes) {
conf.addAll(circumventionProvider.getBridges(bridgeType));
}
controlConnection.setConf(conf);
} else {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
controlConnection.setConf("UseBridges", "0");
}
conf.addAll(circumventionProvider.getBridges(bridgeType));
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
}
@@ -574,6 +590,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("DisableNetwork", "1");
controlConnection.shutdownTor("TERM");
controlSocket.close();
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
@@ -646,7 +664,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to v3 "
+ scrubOnion(onion3) + ": " + e.toString());
+ scrubOnion(onion3) + ": " + e);
}
tryToClose(s, LOG, WARNING);
return null;
@@ -682,8 +700,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
byte[] localSeed = alice ? aliceSeed : bobSeed;
byte[] remoteSeed = alice ? bobSeed : aliceSeed;
String blob = torRendezvousCrypto.getPrivateKeyBlob(localSeed);
String localOnion = torRendezvousCrypto.getOnionAddress(localSeed);
String remoteOnion = torRendezvousCrypto.getOnionAddress(remoteSeed);
String localOnion = torRendezvousCrypto.getOnion(localSeed);
String remoteOnion = torRendezvousCrypto.getOnion(remoteSeed);
TransportProperties remoteProperties = new TransportProperties();
remoteProperties.put(PROP_ONION_V3, remoteOnion);
try {
@@ -705,7 +723,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
});
Map<Integer, String> portLines =
singletonMap(80, "127.0.0.1:" + port);
controlConnection.addOnion(blob, portLines);
try {
controlConnection.addOnion(blob, portLines);
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
return new RendezvousEndpoint() {
@Override
@@ -715,7 +737,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void close() throws IOException {
controlConnection.delOnion(localOnion);
try {
controlConnection.delOnion(localOnion);
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
tryToClose(ss, LOG, WARNING);
}
};
@@ -727,9 +753,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void circuitStatus(String status, String id, String path) {
if (status.equals("BUILT") &&
state.getAndSetCircuitBuilt()) {
LOG.info("First circuit built");
// In case of races between receiving CIRCUIT_ESTABLISHED and setting
// DisableNetwork, set our circuitBuilt flag if not already set
if (status.equals("BUILT") && !state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
}
@@ -740,13 +767,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO))
LOG.info("OR connection " + status + " " + orName);
if (status.equals("CLOSED") || status.equals("FAILED")) {
// Check whether we've lost connectivity
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
}
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CONNECTED")) state.onOrConnectionConnected();
else if (status.equals("CLOSED")) state.onOrConnectionClosed();
}
@Override
@@ -760,24 +784,78 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
state.setBootstrapped();
backoff.reset();
}
}
@Override
public void unrecognized(String type, String msg) {
if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
if (LOG.isLoggable(INFO)) {
String[] words = msg.split(" ");
if (words.length > 1 && ONION_V3.matcher(words[1]).matches()) {
LOG.info("V3 descriptor uploaded");
} else {
LOG.info("V2 descriptor uploaded");
if (type.equals("STATUS_CLIENT")) {
handleClientStatus(removeSeverity(msg));
} else if (type.equals("STATUS_GENERAL")) {
handleGeneralStatus(removeSeverity(msg));
} else if (type.equals("HS_DESC") && msg.startsWith("UPLOADED")) {
String[] parts = msg.split(" ");
if (parts.length < 2) {
LOG.warning("Failed to parse HS_DESC UPLOADED event");
} else if (LOG.isLoggable(INFO)) {
LOG.info("V3 descriptor uploaded for " + scrubOnion(parts[1]));
}
}
}
private String removeSeverity(String msg) {
return msg.replaceFirst("[^ ]+ ", "");
}
private void handleClientStatus(String msg) {
if (msg.startsWith("BOOTSTRAP PROGRESS=100")) {
LOG.info("Bootstrapped");
state.setBootstrapped();
backoff.reset();
} else if (msg.startsWith("CIRCUIT_ESTABLISHED")) {
if (!state.getAndSetCircuitBuilt(true)) {
LOG.info("Circuit built");
backoff.reset();
}
} else if (msg.startsWith("CIRCUIT_NOT_ESTABLISHED")) {
if (state.getAndSetCircuitBuilt(false)) {
LOG.info("Circuit not built");
// TODO: Disable and re-enable network to prompt Tor to rebuild
// its guard/bridge connections? This will also close any
// established circuits, which might still be functioning
}
}
}
private void handleGeneralStatus(String msg) {
if (msg.startsWith("CLOCK_JUMPED")) {
Long time = parseLongArgument(msg, "TIME");
if (time != null && LOG.isLoggable(WARNING)) {
LOG.warning("Clock jumped " + time + " seconds");
}
} else if (msg.startsWith("CLOCK_SKEW")) {
Long skew = parseLongArgument(msg, "SKEW");
if (skew != null && LOG.isLoggable(WARNING)) {
LOG.warning("Clock is skewed by " + skew + " seconds");
}
}
}
@Nullable
private Long parseLongArgument(String msg, String argName) {
String[] args = msg.split(" ");
for (String arg : args) {
if (arg.startsWith(argName + "=")) {
try {
return Long.parseLong(arg.substring(argName.length() + 1));
} catch (NumberFormatException e) {
break;
}
}
}
if (LOG.isLoggable(WARNING)) {
LOG.warning("Failed to parse " + argName + " from '" + msg + "'");
}
return null;
}
@Override
@@ -787,11 +865,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated");
settings = s.getSettings();
// Works around a bug introduced in Tor 0.3.4.8.
// https://trac.torproject.org/projects/tor/ticket/28027
// Could be replaced with callback.transportDisabled()
// when fixed.
disableNetwork();
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
}
@@ -804,16 +877,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void disableNetwork() {
connectionStatusExecutor.execute(() -> {
try {
if (state.isTorRunning()) enableNetwork(false);
} catch (IOException ex) {
logException(LOG, WARNING, ex);
}
});
}
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
@@ -847,8 +910,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
int reasonsDisabled = 0;
boolean enableNetwork = false, enableBridges = false;
boolean enableConnectionPadding = false;
BridgeType bridgeType =
circumventionProvider.getBestBridgeType(country);
List<BridgeType> bridgeTypes =
circumventionProvider.getSuitableBridgeTypes(country);
if (!online) {
LOG.info("Disabling network, device is offline");
@@ -877,10 +940,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (ipv6Only) bridgeType = MEEK;
if (ipv6Only) bridgeTypes = singletonList(MEEK);
enableBridges = true;
if (LOG.isLoggable(INFO)) {
LOG.info("Using bridge type " + bridgeType);
LOG.info("Using bridge types " + bridgeTypes);
}
} else {
LOG.info("Not using bridges");
@@ -898,7 +961,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try {
if (enableNetwork) {
enableBridges(enableBridges, bridgeType);
enableBridges(enableBridges, bridgeTypes);
enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
}
@@ -910,12 +973,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void enableConnectionPadding(boolean enable) throws IOException {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
try {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
}
private void useIpv6(boolean ipv6Only) throws IOException {
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
try {
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
} catch (TorNotRunningException e) {
throw new RuntimeException(e);
}
}
@ThreadSafe
@@ -938,11 +1009,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Nullable
private ServerSocket serverSocket = null;
@GuardedBy("this")
private int orConnectionsConnected = 0;
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private synchronized boolean isTorRunning() {
return started && !stopped;
}
@@ -961,11 +1036,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.pluginStateChanged(getState());
}
private synchronized boolean getAndSetCircuitBuilt() {
boolean firstCircuit = !circuitBuilt;
circuitBuilt = true;
callback.pluginStateChanged(getState());
return firstCircuit;
private synchronized boolean getAndSetCircuitBuilt(boolean built) {
boolean old = circuitBuilt;
circuitBuilt = built;
if (built != old) callback.pluginStateChanged(getState());
return old;
}
private synchronized void enableNetwork(boolean enable) {
@@ -1000,11 +1075,39 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (reasonsDisabled != 0) return DISABLED;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
return bootstrapped && circuitBuilt && orConnectionsConnected > 0
? ACTIVE : ENABLING;
}
private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
}
private synchronized void onOrConnectionConnected() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected++;
logOrConnections();
if (oldConnected == 0) callback.pluginStateChanged(getState());
}
private synchronized void onOrConnectionClosed() {
int oldConnected = orConnectionsConnected;
orConnectionsConnected--;
if (orConnectionsConnected < 0) {
LOG.warning("Count was zero before connection closed");
orConnectionsConnected = 0;
}
logOrConnections();
if (orConnectionsConnected == 0 && oldConnected != 0) {
callback.pluginStateChanged(getState());
}
}
@GuardedBy("this")
private void logOrConnections() {
if (LOG.isLoggable(INFO)) {
LOG.info(orConnectionsConnected + " OR connections connected");
}
}
}
}

View File

@@ -0,0 +1,134 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@Immutable
@NotNullByDefault
abstract class TorPluginFactory implements DuplexPluginFactory {
protected static final Logger LOG =
getLogger(TorPluginFactory.class.getName());
protected static final int MAX_LATENCY = 30 * 1000; // 30 seconds
protected static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
protected final Executor ioExecutor, wakefulIoExecutor;
protected final NetworkManager networkManager;
protected final LocationUtils locationUtils;
protected final EventBus eventBus;
protected final SocketFactory torSocketFactory;
protected final BackoffFactory backoffFactory;
protected final ResourceProvider resourceProvider;
protected final CircumventionProvider circumventionProvider;
protected final BatteryManager batteryManager;
protected final Clock clock;
protected final CryptoComponent crypto;
protected final File torDirectory;
protected final int torSocksPort;
protected final int torControlPort;
TorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
CryptoComponent crypto,
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.resourceProvider = resourceProvider;
this.circumventionProvider = circumventionProvider;
this.batteryManager = batteryManager;
this.clock = clock;
this.crypto = crypto;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
}
@Nullable
abstract String getArchitectureForTorBinary();
abstract TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture);
@Override
public TransportId getId() {
return TorConstants.ID;
}
@Override
public long getMaxLatency() {
return MAX_LATENCY;
}
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
// Check that we have a Tor binary for this architecture
String architecture = getArchitectureForTorBinary();
if (architecture == null) {
LOG.warning("Tor is not supported on this architecture");
return null;
}
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto);
TorPlugin plugin = createPluginInstance(backoff, torRendezvousCrypto,
callback, architecture);
eventBus.addListener(plugin);
return plugin;
}
}

View File

@@ -4,7 +4,7 @@ interface TorRendezvousCrypto {
static final int SEED_BYTES = 32;
String getOnionAddress(byte[] seed);
String getOnion(byte[] seed);
String getPrivateKeyBlob(byte[] seed);
}

View File

@@ -21,9 +21,9 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
}
@Override
public String getOnionAddress(byte[] seed) {
public String getOnion(byte[] seed) {
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
return crypto.encodeOnionAddress(spec.getA().toByteArray());
return crypto.encodeOnion(spec.getA().toByteArray());
}
@Override

View File

@@ -19,6 +19,8 @@ class RecordWriterImpl implements RecordWriter {
private final OutputStream out;
private final byte[] header = new byte[RECORD_HEADER_BYTES];
private long bytesWritten = 0;
RecordWriterImpl(OutputStream out) {
this.out = out;
}
@@ -31,6 +33,7 @@ class RecordWriterImpl implements RecordWriter {
ByteUtils.writeUint16(payload.length, header, 2);
out.write(header);
out.write(payload);
bytesWritten += RECORD_HEADER_BYTES + payload.length;
}
@Override
@@ -42,4 +45,9 @@ class RecordWriterImpl implements RecordWriter {
public void close() throws IOException {
out.close();
}
@Override
public long getBytesWritten() {
return bytesWritten;
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.rendezvous;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.PendingContact;
@@ -42,7 +43,6 @@ import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedE
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful;
import java.security.GeneralSecurityException;

View File

@@ -10,6 +10,7 @@ import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_CONNECT_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
@Module
@@ -20,6 +21,6 @@ public class SocksModule {
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
torSocksPort);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
EXTRA_SOCKET_TIMEOUT);
EXTRA_CONNECT_TIMEOUT, EXTRA_SOCKET_TIMEOUT);
}
}

View File

@@ -26,15 +26,18 @@ class SocksSocket extends Socket {
"Address type not supported"
};
@SuppressWarnings("MismatchedReadAndWriteOfArray")
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
private final SocketAddress proxy;
private final int connectToProxyTimeout, extraSocketTimeout;
private final int connectToProxyTimeout;
private final int extraConnectTimeout, extraSocketTimeout;
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
int extraSocketTimeout) {
int extraConnectTimeout, int extraSocketTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
this.extraConnectTimeout = extraConnectTimeout;
this.extraSocketTimeout = extraSocketTimeout;
}
@@ -66,7 +69,7 @@ class SocksSocket extends Socket {
// Use the supplied timeout temporarily, plus any configured extra
int oldTimeout = getSoTimeout();
setSoTimeout(timeout + extraSocketTimeout);
setSoTimeout(timeout + extraConnectTimeout);
// Connect to the endpoint via the proxy
sendConnectRequest(out, host, port);

View File

@@ -11,18 +11,21 @@ import javax.net.SocketFactory;
class SocksSocketFactory extends SocketFactory {
private final SocketAddress proxy;
private final int connectToProxyTimeout, extraSocketTimeout;
private final int connectToProxyTimeout;
private final int extraConnectTimeout, extraSocketTimeout;
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
int extraSocketTimeout) {
int extraConnectTimeout, int extraSocketTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
this.extraConnectTimeout = extraConnectTimeout;
this.extraSocketTimeout = extraSocketTimeout;
}
@Override
public Socket createSocket() {
return new SocksSocket(proxy, connectToProxyTimeout, extraSocketTimeout);
return new SocksSocket(proxy, connectToProxyTimeout,
extraConnectTimeout, extraSocketTimeout);
}
@Override

View File

@@ -13,11 +13,13 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncConstants;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions;
@@ -47,8 +49,9 @@ 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.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -71,6 +74,16 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
NEXT_SEND_TIME_DECREASED = () -> {
};
/**
* The batch capacity must be at least {@link Record#RECORD_HEADER_BYTES}
* + {@link SyncConstants#MAX_MESSAGE_LENGTH} to ensure that maximum-size
* messages can be selected for transmission. Larger batches will mean
* fewer round-trips between the DB and the output stream, but each
* round-trip will block the DB for longer.
*/
private static final int BATCH_CAPACITY =
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
private final DatabaseComponent db;
private final Executor dbExecutor;
private final EventBus eventBus;
@@ -296,8 +309,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
db.transactionWithNullableResult(false, txn -> {
Collection<Message> batch =
db.generateRequestedBatch(txn, contactId,
MAX_RECORD_PAYLOAD_BYTES,
maxLatency);
BATCH_CAPACITY, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
return batch;
});

View File

@@ -0,0 +1,66 @@
package org.briarproject.bramble.sync;
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.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
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.transport.StreamWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
/**
* A {@link SimplexOutgoingSession} that sends messages eagerly, ie
* regardless of whether they're due for retransmission.
*/
@ThreadSafe
@NotNullByDefault
class EagerSimplexOutgoingSession extends SimplexOutgoingSession {
private static final Logger LOG =
getLogger(EagerSimplexOutgoingSession.class.getName());
EagerSimplexOutgoingSession(DatabaseComponent db,
EventBus eventBus,
ContactId contactId,
TransportId transportId,
long maxLatency,
StreamWriter streamWriter,
SyncRecordWriter recordWriter) {
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
recordWriter);
}
@Override
void sendMessages() throws DbException, IOException {
for (MessageId m : loadUnackedMessageIdsToSend()) {
if (isInterrupted()) break;
Message message = db.transactionWithNullableResult(false, txn ->
db.getMessageToSend(txn, contactId, m, maxLatency, true));
if (message == null) continue; // No longer shared
recordWriter.writeMessage(message);
LOG.info("Sent message");
}
}
private Collection<MessageId> loadUnackedMessageIdsToSend()
throws DbException {
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
db.getUnackedMessagesToSend(txn, contactId));
if (LOG.isLoggable(INFO)) {
LOG.info(ids.size() + " unacked messages to send");
}
return ids;
}
}

View File

@@ -0,0 +1,117 @@
package org.briarproject.bramble.sync;
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.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.DeferredSendHandler;
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.transport.StreamWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Math.min;
import static java.util.Collections.emptyList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
/**
* A {@link SimplexOutgoingSession} for sending and acking messages via a
* mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
* of the messages sent and acked during the session so that they can be
* recorded in the DB as sent or acked after the file has been successfully
* uploaded to the mailbox.
*/
@ThreadSafe
@NotNullByDefault
class MailboxOutgoingSession extends SimplexOutgoingSession {
private static final Logger LOG =
getLogger(MailboxOutgoingSession.class.getName());
private final DeferredSendHandler deferredSendHandler;
private final long initialCapacity;
MailboxOutgoingSession(DatabaseComponent db,
EventBus eventBus,
ContactId contactId,
TransportId transportId,
long maxLatency,
StreamWriter streamWriter,
SyncRecordWriter recordWriter,
DeferredSendHandler deferredSendHandler,
long capacity) {
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
recordWriter);
this.deferredSendHandler = deferredSendHandler;
this.initialCapacity = capacity;
}
@Override
void sendAcks() throws DbException, IOException {
while (!isInterrupted()) {
Collection<MessageId> idsToAck = loadMessageIdsToAck();
if (idsToAck.isEmpty()) break;
recordWriter.writeAck(new Ack(idsToAck));
deferredSendHandler.onAckSent(idsToAck);
LOG.info("Sent ack");
}
}
private Collection<MessageId> loadMessageIdsToAck() throws DbException {
long idCapacity = (getRemainingCapacity() - RECORD_HEADER_BYTES)
/ MessageId.LENGTH;
if (idCapacity <= 0) return emptyList(); // Out of capacity
int maxMessageIds = (int) min(idCapacity, MAX_MESSAGE_IDS);
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
db.getMessagesToAck(txn, contactId, maxMessageIds));
if (LOG.isLoggable(INFO)) {
LOG.info(ids.size() + " messages to ack");
}
return ids;
}
private long getRemainingCapacity() {
return initialCapacity - recordWriter.getBytesWritten();
}
@Override
void sendMessages() throws DbException, IOException {
for (MessageId m : loadMessageIdsToSend()) {
if (isInterrupted()) break;
// Defer marking the message as sent
Message message = db.transactionWithNullableResult(true, txn ->
db.getMessageToSend(txn, contactId, m, maxLatency, false));
if (message == null) continue; // No longer shared
recordWriter.writeMessage(message);
deferredSendHandler.onMessageSent(m);
LOG.info("Sent message");
}
}
private Collection<MessageId> loadMessageIdsToSend() throws DbException {
long capacity = getRemainingCapacity();
if (capacity < RECORD_HEADER_BYTES + MESSAGE_HEADER_LENGTH) {
return emptyList(); // Out of capacity
}
Collection<MessageId> ids = db.transactionWithResult(true, txn ->
db.getMessagesToSend(txn, contactId, capacity, maxLatency));
if (LOG.isLoggable(INFO)) {
LOG.info(ids.size() + " messages to send");
}
return ids;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
@@ -13,9 +12,10 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.SyncConstants;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions;
@@ -23,15 +23,7 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException;
import java.util.ArrayList;
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.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
@@ -40,8 +32,9 @@ 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.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -57,38 +50,40 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private static final Logger LOG =
getLogger(SimplexOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE = () -> {
};
/**
* The batch capacity must be at least {@link Record#RECORD_HEADER_BYTES}
* + {@link SyncConstants#MAX_MESSAGE_LENGTH} to ensure that maximum-size
* messages can be selected for transmission. Larger batches will mean
* fewer round-trips between the DB and the output stream, but each
* round-trip will block the DB for longer.
*/
static final int BATCH_CAPACITY =
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
private final DatabaseComponent db;
private final Executor dbExecutor;
private final EventBus eventBus;
private final ContactId contactId;
private final TransportId transportId;
private final long maxLatency;
private final boolean eager;
private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter;
private final AtomicInteger outstandingQueries;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
protected final DatabaseComponent db;
protected final EventBus eventBus;
protected final ContactId contactId;
protected final TransportId transportId;
protected final long maxLatency;
protected final StreamWriter streamWriter;
protected final SyncRecordWriter recordWriter;
private volatile boolean interrupted = false;
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, TransportId transportId,
long maxLatency, boolean eager, StreamWriter streamWriter,
SimplexOutgoingSession(DatabaseComponent db,
EventBus eventBus,
ContactId contactId,
TransportId transportId,
long maxLatency,
StreamWriter streamWriter,
SyncRecordWriter recordWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.contactId = contactId;
this.transportId = transportId;
this.maxLatency = maxLatency;
this.eager = eager;
this.streamWriter = streamWriter;
this.recordWriter = recordWriter;
outstandingQueries = new AtomicInteger(2); // One per type of record
writerTasks = new LinkedBlockingQueue<>();
}
@IoExecutor
@@ -98,22 +93,13 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
try {
// Send our supported protocol versions
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
// Start a query for each type of record
dbExecutor.execute(this::generateAck);
if (eager) dbExecutor.execute(this::loadUnackedMessageIds);
else dbExecutor.execute(this::generateBatch);
// Write records until interrupted or no more records to write
try {
while (!interrupted) {
ThrowingRunnable<IOException> task = writerTasks.take();
if (task == CLOSE) break;
task.run();
}
streamWriter.sendEndOfStream();
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for a record to write");
Thread.currentThread().interrupt();
sendAcks();
sendMessages();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
streamWriter.sendEndOfStream();
} finally {
eventBus.removeListener(this);
}
@@ -122,11 +108,10 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
@Override
public void interrupt() {
interrupted = true;
writerTasks.add(CLOSE);
}
private void decrementOutstandingQueries() {
if (outstandingQueries.decrementAndGet() == 0) writerTasks.add(CLOSE);
boolean isInterrupted() {
return interrupted;
}
@Override
@@ -146,110 +131,33 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
}
}
@DatabaseExecutor
private void loadUnackedMessageIds() {
if (interrupted) return;
try {
Map<MessageId, Integer> ids = db.transactionWithResult(true, txn ->
db.getUnackedMessagesToSend(txn, contactId));
if (LOG.isLoggable(INFO)) {
LOG.info(ids.size() + " unacked messages to send");
}
if (ids.isEmpty()) decrementOutstandingQueries();
else dbExecutor.execute(() -> generateEagerBatch(ids));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
}
void sendAcks() throws DbException, IOException {
while (!isInterrupted()) if (!generateAndSendAck()) break;
}
@DatabaseExecutor
private void generateEagerBatch(Map<MessageId, Integer> ids) {
if (interrupted) return;
// Take some message IDs from `ids` to form a batch
Collection<MessageId> batchIds = new ArrayList<>();
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();
try {
Collection<Message> batch =
db.transactionWithResult(false, txn ->
db.generateBatch(txn, contactId, batchIds,
maxLatency));
writerTasks.add(() -> writeEagerBatch(batch, ids));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
}
}
@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
private void generateAck() {
if (interrupted) return;
try {
Ack a = db.transactionWithNullableResult(false, txn ->
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if (a == null) decrementOutstandingQueries();
else writerTasks.add(() -> writeAck(a));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
}
}
@IoExecutor
private void writeAck(Ack ack) throws IOException {
if (interrupted) return;
recordWriter.writeAck(ack);
private boolean generateAndSendAck() throws DbException, IOException {
Ack a = db.transactionWithNullableResult(false, txn ->
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if (a == null) return false; // No more acks to send
recordWriter.writeAck(a);
LOG.info("Sent ack");
dbExecutor.execute(this::generateAck);
return true;
}
@DatabaseExecutor
private void generateBatch() {
if (interrupted) return;
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();
}
void sendMessages() throws DbException, IOException {
while (!isInterrupted()) if (!generateAndSendBatch()) break;
}
@IoExecutor
private void writeBatch(Collection<Message> batch) throws IOException {
if (interrupted) return;
for (Message m : batch) recordWriter.writeMessage(m);
private boolean generateAndSendBatch() throws DbException, IOException {
Collection<Message> b = db.transactionWithNullableResult(false, txn ->
db.generateBatch(txn, contactId, BATCH_CAPACITY, maxLatency));
if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null));
if (b == null) return false; // No more messages to send
for (Message m : b) recordWriter.writeMessage(m);
LOG.info("Sent batch");
dbExecutor.execute(this::generateBatch);
return true;
}
}

View File

@@ -85,4 +85,9 @@ class SyncRecordWriterImpl implements SyncRecordWriter {
public void flush() throws IOException {
writer.flush();
}
@Override
public long getBytesWritten() {
return writer.getBytesWritten();
}
}

View File

@@ -64,8 +64,13 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
maxLatency, eager, streamWriter, recordWriter);
if (eager) {
return new EagerSimplexOutgoingSession(db, eventBus, c, t,
maxLatency, streamWriter, recordWriter);
} else {
return new SimplexOutgoingSession(db, eventBus, c, t,
maxLatency, streamWriter, recordWriter);
}
}
@Override

View File

@@ -233,6 +233,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
if (v == null) {
if (LOG.isLoggable(WARNING)) LOG.warning("No validator for " + cv);
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Validating message for " + cv.getClientId());
}
try {
MessageContext context = v.validateMessage(m, g);
storeMessageContextAsync(m, g.getClientId(),
@@ -323,6 +326,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
IncomingMessageHook hook = hooks.get(cv);
if (hook == null) return ACCEPT_DO_NOT_SHARE;
if (LOG.isLoggable(INFO)) {
LOG.info("Delivering message for " + c);
}
try {
return hook.incomingMessage(txn, m, meta);
} catch (DbException e) {

View File

@@ -7,14 +7,13 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.Properties;
import javax.annotation.concurrent.Immutable;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
@Immutable
@NotNullByDefault
@@ -25,14 +24,11 @@ abstract class AbstractSecureRandomProvider implements SecureRandomProvider {
out.writeLong(System.currentTimeMillis());
out.writeLong(System.nanoTime());
out.writeLong(Runtime.getRuntime().freeMemory());
Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
if (ifaces != null) {
for (NetworkInterface i : list(ifaces)) {
for (InetAddress a : list(i.getInetAddresses()))
out.write(a.getAddress());
byte[] hardware = i.getHardwareAddress();
if (hardware != null) out.write(hardware);
}
for (NetworkInterface i : getNetworkInterfaces()) {
for (InetAddress a : list(i.getInetAddresses()))
out.write(a.getAddress());
byte[] hardware = i.getHardwareAddress();
if (hardware != null) out.write(hardware);
}
for (Entry<String, String> e : System.getenv().entrySet()) {
out.writeUTF(e.getKey());

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;

View File

@@ -1,4 +1,6 @@
d Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
d Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
d Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
@@ -9,12 +11,20 @@ d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=s
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
d Bridge obfs4 185.100.85.3:443 5B403DFE34F4872EB027059CECAE30B0C864B3A2 cert=bWUdFUe8io9U6JkSLoGAvSAUDcB779/shovCYmYAQb/pW/iEAMZtO/lCd94OokOF909TPA iat-mode=2
n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cert=t8tA9q2AeGlmp/dO6oW9bkY5RqqmvqjArCEM9wjJoDnk6XtnaejkF0JTA7VamdyOzcvuBg iat-mode=0
n Bridge obfs4 23.88.49.56:443 1CDA1660823AE2565D7F50DE8EB99DFDDE96074B cert=4bwNXedHutVD0ZqCm6ph90Vik9dRY4n9qnBHiLiqQOSsIvui4iHwuMFQK6oqiK8tyhVcDw iat-mode=0
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
n Bridge obfs4 185.161.70.200:9003 7A81D0CD19870DFA3FD13C5DE232D8ADD026DC40 cert=aIcWxyZS3JcWsowlD9hcw9fttA44Bq/W2laFjVWlhuXqrIlAAwrXvq1O9lm9XrkV8GG/ZA iat-mode=0
n Bridge obfs4 172.105.22.69:80 CBD17B33192A879433AB37C9E142541BD3459ABD cert=rk5YmpKypLsjlS4tjkYaZNBweYMa5tWQRhZ8Q2WRleNOgrhSceKo59BA8kp6kVfaMPXnSw iat-mode=0
n Bridge obfs4 46.128.93.192:7346 5D28B8E1D117B8720D56A8513CF32509DCA1D84F cert=ED6tZP50eF0vno09F5gFvoWTMdcWFEX2FtwXOUYRevjzKg30/y701f61Vycnh6HO9gkaMw iat-mode=0
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 83.97.179.29:1199 D83068BFAA28E71DB024B786E1E803BE14257127 cert=IduGtt05tM59Xmvo0oVNWgIRgY4OGPJjFP+y2oa6RMDHQBL/GRyFOOgX70iiazNAIJNkPw iat-mode=0
n Bridge obfs4 207.181.244.13:40132 37FE8D782F5DD2BAEEDAAB8257B701344676B6DD cert=f5Hbfn3ToMzH170cV8DfLly3vRynveidfOfDcbradIDtbLDX15V2yQ8eEH2CPKQJmQR2Hg iat-mode=0
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 176.123.2.253:1933 B855D141CE6C4DE0F7EA4AAED83EBA8373FA8191 cert=1rOoSaRagc6PPR//paIl+ukv1N+xWKCdBXMFxK0k/moEwH0lk5bURBrUDzIX35fVzaiicQ iat-mode=0
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
@@ -20,13 +21,16 @@ 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.AuthorFactory;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.util.StringUtils;
import org.jmock.Expectations;
import org.junit.Test;
@@ -38,18 +42,27 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_AUTHTOKEN;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_INBOXID;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_ONION;
import static org.briarproject.bramble.api.mailbox.MailboxUpdateManager.PROP_KEY_OUTBOXID;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSignaturePrivateKey;
import static org.briarproject.bramble.test.TestUtils.getSignaturePublicKey;
import static org.briarproject.bramble.test.TestUtils.mailboxUpdateEqual;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class ClientHelperImplTest extends BrambleMockTestCase {
@@ -78,13 +91,41 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
private final long timestamp = message.getTimestamp();
private final Metadata metadata = new Metadata();
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
private final String label = StringUtils.getRandomString(5);
private final String label = getRandomString(5);
private final Author author = getAuthor();
private final ClientHelper clientHelper = new ClientHelperImpl(db,
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
metadataEncoder, cryptoComponent, authorFactory);
private final MailboxUpdateWithMailbox validMailboxUpdateWithMailbox;
private final BdfList emptyClientSupports;
private final BdfList someClientSupports;
private final BdfList emptyServerSupports;
private final BdfList someServerSupports;
public ClientHelperImplTest() {
emptyClientSupports = new BdfList();
someClientSupports = BdfList.of(BdfList.of(1, 0));
emptyServerSupports = new BdfList();
someServerSupports = BdfList.of(BdfList.of(1, 0));
validMailboxUpdateWithMailbox = new MailboxUpdateWithMailbox(
singletonList(new MailboxVersion(1, 0)),
getMailboxProperties(false,
singletonList(new MailboxVersion(1, 0))));
}
private BdfDictionary getValidMailboxUpdateWithMailboxDict() {
BdfDictionary dict = new BdfDictionary();
MailboxProperties properties =
validMailboxUpdateWithMailbox.getMailboxProperties();
dict.put(PROP_KEY_ONION, properties.getOnion());
dict.put(PROP_KEY_AUTHTOKEN, properties.getAuthToken());
dict.put(PROP_KEY_INBOXID, properties.getInboxId());
dict.put(PROP_KEY_OUTBOXID, properties.getOutboxId());
return dict;
}
@Test
public void testAddLocalMessage() throws Exception {
boolean shared = new Random().nextBoolean();
@@ -513,4 +554,152 @@ public class ClientHelperImplTest extends BrambleMockTestCase {
will(returnValue(eof));
}});
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateWithEmptyClientSupports()
throws Exception {
BdfDictionary emptyPropsDict = new BdfDictionary();
clientHelper.parseAndValidateMailboxUpdate(emptyClientSupports,
emptyServerSupports, emptyPropsDict
);
}
@Test
public void testParseMailboxUpdateNoMailbox() throws Exception {
BdfDictionary emptyPropsDict = new BdfDictionary();
MailboxUpdate parsedUpdate = clientHelper.parseAndValidateMailboxUpdate(
someClientSupports, emptyServerSupports, emptyPropsDict);
assertFalse(parsedUpdate.hasMailbox());
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateNoMailboxWithSomeServerSupports()
throws Exception {
BdfDictionary emptyPropsDict = new BdfDictionary();
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, emptyPropsDict);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateShortSupports() throws Exception {
clientHelper.parseAndValidateMailboxUpdate(BdfList.of(BdfList.of(1)),
emptyServerSupports, new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateLongSupports() throws Exception {
clientHelper.parseAndValidateMailboxUpdate(
BdfList.of(BdfList.of(1, 0, 0)), emptyServerSupports,
new BdfDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateNonIntSupports() throws Exception {
clientHelper.parseAndValidateMailboxUpdate(
BdfList.of(BdfList.of(1, "0")), emptyServerSupports,
new BdfDictionary()
);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateNonListSupports() throws Exception {
clientHelper.parseAndValidateMailboxUpdate(
BdfList.of("non-list"), emptyServerSupports,
new BdfDictionary());
}
@Test
public void testParseValidMailboxUpdateWithMailbox() throws Exception {
MailboxUpdate parsedUpdate = clientHelper.parseAndValidateMailboxUpdate(
someClientSupports, someServerSupports,
getValidMailboxUpdateWithMailboxDict());
assertTrue(
mailboxUpdateEqual(validMailboxUpdateWithMailbox,
parsedUpdate));
}
@Test(expected = FormatException.class)
public void rejectsMailboxUpdateWithEmptyServerSupports() throws Exception {
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
emptyServerSupports, getValidMailboxUpdateWithMailboxDict());
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateOnionNotDecodable() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
String badOnion = "!" + propsDict.getString(PROP_KEY_ONION)
.substring(1);
propsDict.put(PROP_KEY_ONION, badOnion);
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
emptyServerSupports, propsDict);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateOnionWrongLength() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
String tooLongOnion = propsDict.getString(PROP_KEY_ONION) + "!";
propsDict.put(PROP_KEY_ONION, tooLongOnion);
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
emptyServerSupports, propsDict
);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateInboxIdWrongLength() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
propsDict.put(PROP_KEY_INBOXID, getRandomBytes(UniqueId.LENGTH + 1));
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, propsDict);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateOutboxIdWrongLength() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
propsDict.put(PROP_KEY_OUTBOXID, getRandomBytes(UniqueId.LENGTH + 1));
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, propsDict);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateAuthTokenWrongLength()
throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
propsDict.put(PROP_KEY_AUTHTOKEN, getRandomBytes(UniqueId.LENGTH + 1));
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, propsDict);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateMissingOnion() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
propsDict.remove(PROP_KEY_ONION);
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, propsDict
);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateMissingAuthToken() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
propsDict.remove(PROP_KEY_AUTHTOKEN);
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, propsDict);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateMissingInboxId() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
propsDict.remove(PROP_KEY_INBOXID);
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, propsDict);
}
@Test(expected = FormatException.class)
public void testRejectsMailboxUpdateMissingOutboxId() throws Exception {
BdfDictionary propsDict = getValidMailboxUpdateWithMailboxDict();
propsDict.remove(PROP_KEY_OUTBOXID);
clientHelper.parseAndValidateMailboxUpdate(someClientSupports,
someServerSupports, propsDict);
}
}

View File

@@ -72,6 +72,7 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
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.VISIBLE;
@@ -94,11 +95,15 @@ import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class DatabaseComponentImplTest extends BrambleMockTestCase {
private static final int BATCH_CAPACITY =
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
@SuppressWarnings("unchecked")
private final Database<Object> database = context.mock(Database.class);
private final ShutdownManager shutdownManager =
@@ -298,11 +303,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not)
exactly(19).of(database).startTransaction();
exactly(25).of(database).startTransaction();
will(returnValue(txn));
exactly(19).of(database).containsContact(txn, contactId);
exactly(25).of(database).containsContact(txn, contactId);
will(returnValue(false));
exactly(19).of(database).abortTransaction(txn);
exactly(25).of(database).abortTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
@@ -356,6 +361,39 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected
}
try {
db.transaction(false, transaction ->
db.getMessageToSend(transaction, contactId, messageId, 123,
true));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.getMessagesToAck(transaction, contactId, 123));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.getMessagesToSend(transaction, contactId, 123, 456));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.getUnackedMessagesToSend(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.getUnackedMessageBytesToSend(transaction, contactId));
@@ -439,6 +477,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected
}
try {
db.transaction(false, transaction ->
db.setAckSent(transaction, contactId,
singletonList(messageId)));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.setContactAlias(transaction, contactId, alias));
@@ -456,6 +503,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected
}
try {
db.transaction(false, transaction ->
db.setMessagesSent(transaction, contactId,
singletonList(messageId), 123));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.setSyncVersions(transaction, contactId, emptyList()));
@@ -918,15 +974,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).getMessagesToSend(txn, contactId,
MAX_MESSAGE_LENGTH * 2, maxLatency);
BATCH_CAPACITY, maxLatency);
will(returnValue(ids));
// First message
oneOf(database).getMessage(txn, messageId);
will(returnValue(message));
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
maxLatency);
// Second message
oneOf(database).getMessage(txn, messageId1);
will(returnValue(message1));
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
maxLatency);
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
oneOf(database).commitTransaction(txn);
@@ -937,7 +995,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.transaction(false, transaction ->
assertEquals(messages, db.generateBatch(transaction, contactId,
MAX_MESSAGE_LENGTH * 2, maxLatency)));
BATCH_CAPACITY, maxLatency)));
}
@Test
@@ -951,9 +1009,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(database).getMessagesToOffer(txn, contactId, 123, maxLatency);
will(returnValue(ids));
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
maxLatency);
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
oneOf(database).updateRetransmissionData(txn, contactId, messageId1,
maxLatency);
oneOf(database).commitTransaction(txn);
}});
@@ -1001,16 +1059,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).getRequestedMessagesToSend(txn, contactId,
MAX_MESSAGE_LENGTH * 2, maxLatency);
BATCH_CAPACITY, maxLatency);
will(returnValue(ids));
// First message
oneOf(database).getMessage(txn, messageId);
will(returnValue(message));
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId,
maxLatency);
oneOf(database).updateRetransmissionData(txn, contactId,
messageId, maxLatency);
// Second message
oneOf(database).getMessage(txn, messageId1);
will(returnValue(message1));
oneOf(database).updateExpiryTimeAndEta(txn, contactId, messageId1,
maxLatency);
oneOf(database).updateRetransmissionData(txn, contactId,
messageId1, maxLatency);
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
@@ -1020,7 +1080,73 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.transaction(false, transaction ->
assertEquals(messages, db.generateRequestedBatch(transaction,
contactId, MAX_MESSAGE_LENGTH * 2, maxLatency)));
contactId, BATCH_CAPACITY, maxLatency)));
}
@Test
public void testGetMessageToSendMessageNotVisible() throws Exception {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction ->
assertNull(db.getMessageToSend(transaction, contactId,
messageId, maxLatency, false)));
}
@Test
public void testGetMessageToSendMessageNotMarkedAsSent() throws Exception {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).getMessage(txn, messageId);
will(returnValue(message));
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction ->
assertEquals(message, db.getMessageToSend(transaction,
contactId, messageId, maxLatency, false)));
}
@Test
public void testGetMessageToSendMessageMarkedAsSent() throws Exception {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).getMessage(txn, messageId);
will(returnValue(message));
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
maxLatency);
oneOf(database).lowerRequestedFlag(txn, contactId,
singletonList(messageId));
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction ->
assertEquals(message, db.getMessageToSend(transaction,
contactId, messageId, maxLatency, true)));
}
@Test
@@ -1245,6 +1371,62 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.receiveRequest(transaction, contactId, r));
}
@Test
public void testSetAckSent() throws Exception {
Collection<MessageId> acked = asList(messageId, messageId1);
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
// First message is still visible to the contact - flag lowered
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
// Second message is no longer visible - flag not lowered
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
will(returnValue(false));
oneOf(database)
.lowerAckFlag(txn, contactId, singletonList(messageId));
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction ->
db.setAckSent(transaction, contactId, acked));
}
@Test
public void testSetMessagesSent() throws Exception {
long maxLatency = 123456;
Collection<MessageId> sent = asList(messageId, messageId1);
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
// First message is still visible to the contact - mark as sent
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
will(returnValue(true));
oneOf(database).getMessageLength(txn, messageId);
will(returnValue(message.getRawLength()));
oneOf(database).updateRetransmissionData(txn, contactId, messageId,
maxLatency);
// Second message is no longer visible - don't mark as sent
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
will(returnValue(false));
oneOf(database).lowerRequestedFlag(txn, contactId,
singletonList(messageId));
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction ->
db.setMessagesSent(transaction, contactId, sent, maxLatency));
}
@Test
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
throws Exception {

View File

@@ -33,7 +33,9 @@ import java.util.Random;
import java.util.logging.Logger;
import static java.util.logging.Level.OFF;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
@@ -97,6 +99,9 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
// All our transports use a maximum latency of 30 seconds
private static final int MAX_LATENCY = 30 * 1000;
private static final int BATCH_CAPACITY =
(RECORD_HEADER_BYTES + MAX_MESSAGE_LENGTH) * 2;
protected final File testDir = getTestDirectory();
private final File resultsFile = new File(getTestName() + ".tsv");
protected final Random random = new Random();
@@ -471,7 +476,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
benchmark(name, db -> {
Connection txn = db.startTransaction();
db.getMessagesToSend(txn, pickRandom(contacts).getId(),
MAX_MESSAGE_IDS, MAX_LATENCY);
BATCH_CAPACITY, MAX_LATENCY);
db.commitTransaction(txn);
});
}
@@ -522,7 +527,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
benchmark(name, db -> {
Connection txn = db.startTransaction();
db.getRequestedMessagesToSend(txn, pickRandom(contacts).getId(),
MAX_MESSAGE_IDS, MAX_LATENCY);
BATCH_CAPACITY, MAX_LATENCY);
db.commitTransaction(txn);
});
}

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