Compare commits

...

178 Commits

Author SHA1 Message Date
akwizgran
4df523aaf8 Bump version numbers for 1.4.5 release. 2022-02-24 16:06:34 +00:00
akwizgran
6783eae1b1 Update bridges. 2022-02-24 15:32:57 +00:00
akwizgran
fe58bd8f86 Update translations. 2022-02-24 11:11:17 +00:00
akwizgran
1a1b26d8f2 Merge branch 'ci-manual-fix' into 'master'
Fix required manual tests

See merge request briar/briar!1588
2022-02-21 12:33:43 +00:00
akwizgran
4e90641059 Merge branch 'onion-address-fix' into 'master'
Fix merge request race condition

See merge request briar/briar!1589
2022-02-18 15:33:00 +00:00
Torsten Grote
f7892050ea allow_failure of mailbox test to make it optional again 2022-02-18 12:18:37 -03:00
Torsten Grote
003ecdb81f Fix merge request race condition 2022-02-18 12:02:06 -03:00
Torsten Grote
9141a8bb3b Merge branch '2168-mailbox-pairing-backend' into 'master'
Implement backend for pairing mailbox

Closes #2168

See merge request briar/briar!1587
2022-02-18 14:56:44 +00:00
akwizgran
7ba2af077e Merge branch '2162-mailbox-pairing-ui' into 'master'
Implement UI for pairing Briar with mailbox

See merge request briar/briar!1585
2022-02-18 14:50:10 +00:00
Torsten Grote
ce7f44de01 Set our own mailbox status right after pairing 2022-02-18 11:01:04 -03:00
Torsten Grote
4a46b13e9d Address mailbox pairing backend review feedback 2022-02-18 09:50:38 -03:00
Torsten Grote
ae7ccdf34c Tweak wording of mailbox intro and download instructions 2022-02-18 09:28:50 -03:00
Torsten Grote
88c54ed3b0 Rename getOnionAddress() to getBaseUrl()
This can later include a version parameter as well.
2022-02-18 09:16:51 -03:00
Torsten Grote
653b744a02 Add getMailboxStatus method to MailboxManager 2022-02-18 09:16:51 -03:00
Torsten Grote
65e7bcb94e Add unit tests for MailboxPairingTask 2022-02-18 09:16:51 -03:00
Torsten Grote
d6bbe59d3a Implement backend for pairing mailbox 2022-02-18 09:16:50 -03:00
Torsten Grote
98dddf3572 Make hiding ActionBar up/back button in Final Fragment optional 2022-02-16 10:37:27 -03:00
akwizgran
6d22bab5ee Merge branch 'transactional-remove-pending-contact' into 'master'
Add transactional version of removePendingContact()

See merge request briar/briar!1586
2022-02-16 11:18:31 +00:00
Sebastian Kürten
7ae91a984f Add transactional version of removePendingContact() 2022-02-15 22:28:29 +01:00
Torsten Grote
fb50a5ba45 Remove custom back and action bar code from OfflineFragment
Also pop the offline fragment off the stack when trying again, so it doesn't show up again when we are not offline anymore.
2022-02-14 14:56:39 -03:00
Torsten Grote
80bc409225 Remove 'share mailbox download link' button 2022-02-14 13:47:04 -03:00
akwizgran
80cac277ac Merge branch '2257-mailbox-wipe' into 'master'
Add method for wiping the mailbox

Closes #2257

See merge request briar/briar!1582
2022-02-14 14:45:51 +00:00
Torsten Grote
888aea4b37 Scroll fragments down on small screens to make bottom buttons visible 2022-02-10 09:46:43 -03:00
Torsten Grote
e9d3f600fa Improve mailbox pairing strings 2022-02-10 09:43:28 -03:00
Torsten Grote
3055338ea8 Show offline fragment before scanning mailbox QR code 2022-02-10 09:41:03 -03:00
Torsten Grote
e4a7b1731a Do real check if mailbox is set up 2022-02-10 09:41:03 -03:00
Torsten Grote
2da8c19d3e Handle TorPlugin not being active during mailbox setup 2022-02-10 09:41:03 -03:00
Torsten Grote
237ac50b01 Handle scanning a wrong QR code
when pairing a mailbox.
2022-02-10 09:41:02 -03:00
Torsten Grote
73d9e05ada Scan Mailbox QR code for setup and show progress screen 2022-02-10 09:17:39 -03:00
Torsten Grote
e14773985d Show mailbox onboarding/download info
if the mailbox is not yet set up.
2022-02-10 08:51:58 -03:00
Torsten Grote
8b3dae6daf Add Mailbox entry into settings 2022-02-10 08:51:58 -03:00
Torsten Grote
065ceb8e98 Add FeatureFlag for mailbox 2022-02-10 08:51:58 -03:00
akwizgran
6d881892c7 Merge branch '2231-file-api' into 'master'
Add Mailbox File Mangement API

Closes #2233, #2232, and #2231

See merge request briar/briar!1581
2022-02-10 11:50:15 +00:00
Torsten Grote
16b503dd7b Introduce MailboxId sub-classes for even more type-safety 2022-02-07 15:58:54 -03:00
Torsten Grote
fc5533ec6e Add method for wiping the mailbox 2022-02-07 09:37:05 -03:00
Torsten Grote
5c153aeb6c Sort files returned by getFiles by time (oldest first). 2022-02-07 09:36:48 -03:00
akwizgran
36670a8bf6 Bump version numbers for 1.4.4 release. 2022-01-27 11:56:54 +00:00
akwizgran
32d62f9960 Update translations. 2022-01-27 11:56:10 +00:00
akwizgran
eafd6a1ca1 Merge branch '2143-security-exception-image-loading' into 'master'
Add FLAG_GRANT_READ_URI_PERMISSION when getting content

Closes #2143

See merge request briar/briar!1583
2022-01-27 11:47:03 +00:00
Torsten Grote
1614e72c43 Add FLAG_GRANT_READ_URI_PERMISSION when getting content 2022-01-27 07:57:51 -03:00
Torsten Grote
d3beb850ef Factor out getArray() for easier JSON parsing 2022-01-24 14:03:48 -03:00
Torsten Grote
f057f0859b Use MailboxId instead of String for type-safety 2022-01-24 13:50:58 -03:00
Torsten Grote
61ea7ff8de Make deleting a non-existent file is tolerable 2022-01-21 15:12:14 -03:00
Torsten Grote
0fba65a722 Add integration test for File Management API 2022-01-21 15:12:13 -03:00
Torsten Grote
3a191908c0 Add method for listing folders with files available
for download (owner only)
2022-01-21 15:12:13 -03:00
Torsten Grote
482258fc92 Add method for deleting a file from a mailbox 2022-01-21 15:11:47 -03:00
Torsten Grote
0cb2dcf6b7 Add method for downloading a file from a mailbox 2022-01-21 15:11:46 -03:00
Torsten Grote
76599a8d04 Add method for listing files from mailbox 2022-01-21 15:11:46 -03:00
Torsten Grote
173af62dec Add method for adding file to mailbox 2022-01-21 15:11:46 -03:00
akwizgran
a53a49e543 Merge branch '2250-refuse-to-start-if-android4-expired' into 'master'
Refuse to start app on Android 4 beyond expiry date

Closes #2250

See merge request briar/briar!1578
2022-01-18 15:03:25 +00:00
akwizgran
78b993bda4 Merge branch 'mailbox-integration-test' into 'master'
Add MailboxIntegrationTest against a real mailbox instance

See merge request briar/briar!1575
2022-01-18 14:46:38 +00:00
akwizgran
6b956611e7 Merge branch '2251-warn-briar-expires-on-android4' into 'master'
Show expiry warning when running on Android 4

Closes #2251

See merge request briar/briar!1577
2022-01-18 14:36:33 +00:00
Torsten Grote
d0c3c1f9f6 Move wait-for-mailbox.sh to bramble-core/src/test/bash 2022-01-18 11:33:08 -03:00
Torsten Grote
24d058cdcc Merge branch 'add-more-feature-flags-for-desktop' into 'master'
Add feature flags for private groups, forums and blogs

See merge request briar/briar!1572
2022-01-18 11:41:26 +00:00
Torsten Grote
a9ab7fd60f Do not kill mailbox at the end of integration test 2022-01-17 11:10:49 -03:00
Daniel Lublin
9e5201d571 Don't try to sign out; we know we aren't signed in 2022-01-17 13:09:40 +01:00
Daniel Lublin
39eebe4c02 Remove, BaseActivity does this 2022-01-17 11:21:49 +01:00
Daniel Lublin
171df265ab Let our activity actually show up, by avoiding to extend BriarActivity
If user needs to sign in or create an account, BriarActivity launches
StartupActivity. But we want to show up before that.

Implement our own signOut with BriarActivity as a template.
2022-01-17 11:04:50 +01:00
Daniel Lublin
9436757215 Reuse existing string (same kind of context) 2022-01-13 12:59:55 +01:00
Daniel Lublin
75370c8124 Refuse to start on Android 4 beyond the set expiry date 2022-01-13 12:59:55 +01:00
Daniel Lublin
10dceafde1 Show expiry warning when running on Android 4
After a set date a snackbar is shown, warning that Briar will stop
working at a later set date.
2022-01-13 12:59:03 +01:00
akwizgran
e3126f931e Merge branch 'move-proguard-rules' into 'master'
Move ProGuard rules for Briar to briar-android

See merge request briar/briar!1580
2022-01-13 11:44:38 +00:00
akwizgran
6ddedbba36 Move ProGuard rules for Briar to briar-android. 2022-01-13 10:37:10 +00:00
akwizgran
982637a0b0 Merge branch 'correct-proguard' into 'master'
Restore proguard rule to keep us alive

See merge request briar/briar!1579
2022-01-13 10:35:37 +00:00
Daniel Lublin
78ef8c8117 Restore proguard, letting us stay alive by keeping more classes
For one, fragment classes referenced *only* from settings.xml (in
app:fragment-attributes) where not pulled in. Accessing such settings
would cause a crash.
2022-01-12 13:51:44 +01:00
Torsten Grote
7319398c3b Merge branch 'dex-method-limit' into 'master'
Update ProGuard rules to stay within dex method limit

See merge request briar/briar!1576
2022-01-11 14:45:22 +00:00
akwizgran
841b8133d1 Update ProGuard rules to stay within dex method limit. 2022-01-11 10:50:49 +00:00
akwizgran
b334e8da27 Suppress ProGuard warning about Jackson's Java7SupportImpl. 2022-01-11 10:48:56 +00:00
akwizgran
0ac26883c6 Build APK for CI pipelines. 2022-01-11 10:48:03 +00:00
Torsten Grote
519837e829 Add MailboxIntegrationTest against a real mailbox instance 2022-01-07 15:34:19 -03:00
Torsten Grote
9fa54bf15c Actually throw TolerableFailureException when *deleting* a contact
Before, this was accidentally added to *listing* contacts.
2022-01-07 14:29:14 -03:00
akwizgran
af3389e0e1 Merge branch '2187-delete-contact-from-mailbox' into 'master'
Add method for deleting a contact and retrieving contact list from own mailbox

Closes #2182 and #2187

See merge request briar/briar!1574
2022-01-07 15:13:19 +00:00
Torsten Grote
f5cdad9100 Throw TolerableFailureException when deleting a contact returns 404 2022-01-07 12:03:21 -03:00
Torsten Grote
df4e6aa207 Add method for retrieving contact list from own mailbox 2022-01-07 11:33:57 -03:00
Torsten Grote
82443d9708 Add method for deleting a contact from own mailbox 2022-01-07 10:46:43 -03:00
akwizgran
27058ba0ca Merge branch '2183-mailbox-add-contact' into 'master'
Add method for adding a contact to own mailbox

Closes #2183

See merge request briar/briar!1573
2022-01-07 13:37:40 +00:00
Torsten Grote
f400cf5aa0 Throw ApiException when adding contact is not successful 2022-01-07 10:22:22 -03:00
Torsten Grote
e52c5ddc8e Rename PermanentFailureException to ApiException 2022-01-07 10:13:31 -03:00
Torsten Grote
835e9f6994 Add mailbox API endpoint for adding a contact 2022-01-07 10:13:31 -03:00
akwizgran
4193179eb8 Merge branch '2243-okhttp-mailbox-calls' into 'master'
Add /status mailbox API call and a test for it

Closes #2207 and #2243

See merge request briar/briar!1564
2022-01-07 13:10:49 +00:00
Torsten Grote
421b00517f Address review comments for MailboxApi 2022-01-07 09:51:29 -03:00
Sebastian Kürten
707802c459 Add feature flags for private groups, forums and blogs 2022-01-06 15:35:37 +01:00
Torsten Grote
9f1757ccaf Remove concept of fatal permanent exceptions
All exceptions will just cause the request to be tried again with some backoff.
2022-01-03 14:12:21 -03:00
Torsten Grote
d665fc17ec Add /status and /setup mailbox API call with tests 2022-01-03 14:10:44 -03:00
akwizgran
65be2d2b26 Merge branch 'transactional-attachment-reader' into 'master'
Transactional version of AttachmentReader#getAttachment()

See merge request briar/briar!1570
2021-12-23 17:05:32 +00:00
Sebastian Kürten
d2a39da3e0 Transactional version of AttachmentReader#getAttachment() 2021-12-20 13:55:08 +01:00
akwizgran
d13e4c976e Merge branch 'fix-string-substitutions' into 'master'
Fix multiple substitutions specified in non-positional format

See merge request briar/briar!1568
2021-12-17 11:23:03 +00:00
akwizgran
20b52804bf Merge branch 'add-pending-contact-transactional' into 'master'
Transactional versions of some more API calls

See merge request briar/briar!1561
2021-12-16 16:38:15 +00:00
ialokim
5b27eb354c transactional versions of addPendingContact, getPendingContacts, getConversationId and respondToIntroduction 2021-12-16 17:21:57 +01:00
Torsten Grote
c340071469 Merge branch 'jmock-java-11' into 'master'
Replace ClassImposteriser with ByteBuddyClassImposteriser

See merge request briar/briar!1569
2021-12-15 16:34:04 +00:00
akwizgran
506e274dff Merge branch '2242-migrate-okhttp' into 'master'
Migrate OkHttp to bramble-core

Closes #2242

See merge request briar/briar!1562
2021-12-15 16:04:33 +00:00
Torsten Grote
423356fdda Add missing bouncycastle dependency to witness 2021-12-15 10:48:55 -03:00
Torsten Grote
043a173828 Migrate OkHttp to bramble-core 2021-12-15 10:46:48 -03:00
akwizgran
f0501bbfab Merge branch '1483-peer-session-crash' into 'master'
Do not create PeerSession for groups we created

Closes #1483

See merge request briar/briar!1344
2021-12-15 12:37:46 +00:00
akwizgran
5cafde7b14 Merge branch 'test-deps' into 'master'
Upgrade briar-android unit test dependencies

See merge request briar/briar!1567
2021-12-15 12:34:54 +00:00
akwizgran
5117dbad7e Merge branch 'bouncycastle-java8' into 'master'
Switch to bouncycastle dependency for Java 1.8 to prevent Java 15 class warnings of multi-jar

See merge request briar/briar!1566
2021-12-15 10:39:38 +00:00
Torsten Grote
3a22388495 Fix multiple substitutions specified in non-positional format
The build warnings might persist until translations have been updated
2021-12-14 16:51:52 -03:00
Torsten Grote
1d4de46dfd Upgrade briar-android unit test dependencies 2021-12-14 16:22:11 -03:00
Torsten Grote
d805069dfe Switch to bouncycastle dependency for Java 1.8 to prevent Java 15 class warnings of multi-jar 2021-12-14 15:42:10 -03:00
akwizgran
74cb2a6ce5 Merge branch 'readme-reproducible' into 'master'
Add a section about reproducible builds to the readme

See merge request briar/briar!1565
2021-12-14 13:23:46 +00:00
Torsten Grote
2880a4adac Add a section about reproducible builds to the readme 2021-12-14 10:00:20 -03:00
akwizgran
e032e0ccd5 Bump version numbers for 1.4.3 release. 2021-12-13 16:58:13 +00:00
Torsten Grote
38a07e1d54 Merge branch 'fix-headless-jar-build-on-java-11' into 'master'
Fix headless jar build on Java 11, add to CI pipeline

See merge request briar/briar!1563
2021-12-13 16:22:43 +00:00
akwizgran
07b35db4d4 Fix headless jar build on Java 11, add to CI pipeline. 2021-12-13 15:38:17 +00:00
akwizgran
3b03db9f43 Bump version numbers for 1.4.2 release. 2021-12-13 15:17:34 +00:00
akwizgran
de3a74eedf Update translations. 2021-12-13 15:16:17 +00:00
akwizgran
5a39f9730f Merge branch 'non-default-bridges' into 'master'
Use non-default obfs4 bridges in Russia

See merge request briar/briar!1556
2021-12-13 13:34:19 +00:00
akwizgran
bdf02bbc6c Increase test timeout because meek bridge is super slow. 2021-12-13 12:53:42 +00:00
akwizgran
d5b2ebdb23 Include meek bridge in test and mark it as essential. 2021-12-13 11:56:52 +00:00
Torsten Grote
98bb8d4af1 Merge branch 'fix-database-locking' into 'master'
Use ReentrantLock as a lock (not a monitor) when marking DB as open.

See merge request briar/briar!1549
2021-12-13 11:48:28 +00:00
akwizgran
75cc19e578 Remove failing non-default bridge. 2021-12-13 11:47:03 +00:00
akwizgran
aad87e6e98 Update default bridges from upstream. 2021-12-13 11:46:43 +00:00
akwizgran
dad895c30d Merge branch '2225-error-handling-for-mailbox-downloads' into 'master'
Add methods to get StreamContext from tag, and mark it as recognised

Closes #2225

See merge request briar/briar!1560
2021-12-10 14:45:34 +00:00
Daniel Lublin
f8b3d79813 Add methods to get StreamContext from tag, and mark it as recognised
Separate methods are needed to be able to restart reading from a stream
in the case of errors. Tag should be marked as recognised only after
successfully reading the stream.

Closes #2225
2021-12-10 15:23:30 +01:00
akwizgran
0a98566298 Merge branch '2190-mailbox-reset-retransmission-times' into 'master'
Resolve "Method for resetting retransmission times"

Closes #2190

See merge request briar/briar!1559
2021-12-09 12:11:54 +00:00
Daniel Lublin
93a03d7e15 Reset using a single db query 2021-12-09 12:55:01 +01:00
Daniel Lublin
3eb3dbde09 Add database method to reset retransmission times
Will be used to ensure messages are not stranded on a Mailbox, when such
is added, removed, or otherwise changed.

Closes #2190.
2021-12-09 11:10:50 +01:00
akwizgran
fd56176450 Merge branch 'desktop-98-transactional' into 'master'
Add transactional versions of some API calls for Briar Desktop

See merge request briar/briar!1557
2021-12-08 11:08:47 +00:00
ialokim
d29812f055 add transactional versions of some API calls 2021-12-07 20:39:55 +01:00
akwizgran
403601b9f2 Merge branch '2088-fix-error-with-null-group' into 'master'
Offline hotspot: don't crash if group is null

Closes #2088

See merge request briar/briar!1552
2021-12-07 15:20:35 +00:00
akwizgran
992215b78a Merge branch '2230-tracking-uploads' into 'master'
Add MailboxSettingsManager methods for tracking pending uploads

Closes #2230

See merge request briar/briar!1558
2021-12-07 15:12:24 +00:00
Torsten Grote
658ca8de21 Add MailboxSettingsManager methods for tracking pending uploads 2021-12-07 10:07:54 -03:00
akwizgran
e0e2c0cc89 Add unit test. 2021-12-07 10:33:16 +00:00
akwizgran
114d80ad43 Use non-default obfs4 bridges in Russia. 2021-12-06 18:11:16 +00:00
Torsten Grote
8d5803098b Merge branch 'network-usage-metrics' into 'master'
Include network usage metrics in crash reports and feedback

See merge request briar/briar!1555
2021-11-30 18:08:07 +00:00
akwizgran
a9ed9da822 Include network usage metrics in crash reports and feedback. 2021-11-30 14:12:45 +00:00
akwizgran
1d04bbcb4f Merge branch '1607-share-http-client-instance' into 'master'
Use a single OkHttpClient instance for all RSS fetches

Closes #1607

See merge request briar/briar!1536
2021-11-23 11:51:23 +00:00
akwizgran
43b0d1d543 Merge branch 'gradle-7-3' into 'master'
Upgrade gradle to v7.3

See merge request briar/briar!1553
2021-11-23 11:43:47 +00:00
Sebastian Kürten
1bfd9e4eb0 Upgrade gradle to v7.3 2021-11-23 10:13:11 +01:00
Sebastian Kürten
2295db4361 Offline hotspot: don't crash if group is null 2021-11-22 17:26:01 +01:00
akwizgran
8fca06e040 Merge branch 'gradle-7' into 'master'
Upgrade gradle and android plugin to v7

See merge request briar/briar!1551
2021-11-22 15:36:30 +00:00
akwizgran
3f7c9af3a9 Create the HTTP client lazily and allow it to be garbage collected. 2021-11-22 12:01:51 +00:00
akwizgran
93178d2f28 Merge branch '2171-store-time-of-last-connection-to-own-mailbox' into 'master'
Store time of last attempted and successful connections to own mailbox

Closes #2171

See merge request briar/briar!1547
2021-11-22 11:09:37 +00:00
akwizgran
2755d3f470 Merge branch '2169-store-own-mailbox-properties' into 'master'
Add mailbox settings manager

Closes #2169

See merge request briar/briar!1546
2021-11-22 11:09:03 +00:00
Torsten Grote
7efc3ca78f Fix AuthorView using the wrong constant 2021-11-19 15:11:23 -03:00
Torsten Grote
d6767a62b9 Upgrade gradle and android plugin to v7 2021-11-19 14:55:16 -03:00
Torsten Grote
2821460648 Merge branch 'use-synchroniser-for-mock-tests' into 'master'
Use Synchroniser for all mock tests

See merge request briar/briar!1550
2021-11-18 13:37:16 +00:00
akwizgran
7aa1073bf5 Replace ClassImposteriser with ByteBuddyClassImposteriser.
This may avoid problems with ClassImposteriser when using Java 11. See
https://github.com/jmock-developers/jmock-library/releases/tag/2.10.0
2021-11-18 13:28:08 +00:00
akwizgran
3ff7349b40 Set threading policy for the one test that does things differently. 2021-11-18 13:27:38 +00:00
akwizgran
22593722a7 Convert remaining tests to use BrambleMockTestCase. 2021-11-18 12:56:39 +00:00
akwizgran
e91ad962cb Use Synchroniser for all mock tests.
Android Studio tries to collect the output of failed tests on a different thread, which results in an exception when using Mockery's default threading policy, causing the tests to appear as incomplete rather than failed.
2021-11-18 11:32:33 +00:00
akwizgran
53d9a9b43b Merge branch '2153-tor-0.3.5.17' into 'master'
Upgrade Tor to 0.3.5.17 and remove v2 onion code

Closes #1280 and #2153

See merge request briar/briar!1548
2021-11-17 15:40:44 +00:00
akwizgran
6c702bad0a Use ReentrantLock as a lock (not a monitor) when marking DB as open. 2021-11-17 15:03:58 +00:00
Torsten Grote
0dc2aba22f Remove old v2 onion code from TorPlugin 2021-11-17 10:54:49 -03:00
Torsten Grote
5a8b822e08 Upgrade Tor to 0.3.5.17 2021-11-17 09:46:49 -03:00
akwizgran
8ac6b0155b Store time of last attempted and successful connections to mailbox. 2021-11-17 10:49:00 +00:00
akwizgran
372810f48e Add mailbox settings manager. 2021-11-16 12:53:17 +00:00
akwizgran
1b4ab4f945 Merge branch '2167-decode-qr-code-for-pairing-with-mailbox' into 'master'
Create initial MailboxPairViewModel that decodes QR code

Closes #2167

See merge request briar/briar!1544
2021-11-16 12:15:26 +00:00
Daniel Lublin
407ddad0a8 Make it package private 2021-11-15 12:59:57 +01:00
Daniel Lublin
77a986318e Add MailboxPairViewModel that decodes Mailbox QR code 2021-11-15 12:59:56 +01:00
Daniel Lublin
1809be4656 Factor out onion address encoder to CryptoComponent 2021-11-15 12:59:56 +01:00
Daniel Lublin
6d1a0a5792 Refactor qrcode and its camera classes to separate package 2021-11-15 12:59:56 +01:00
akwizgran
85dc27ed77 Merge branch 'message-tracked-event' into 'master'
Implement and use new message tracked event

See merge request briar/briar!1541
2021-11-12 11:35:59 +00:00
ialokim
3f8df34f5c use new event to update contacts view model 2021-11-11 20:56:13 +01:00
ialokim
eb08781460 broadcast event when a conversation message is tracked 2021-11-11 20:56:02 +01:00
akwizgran
8a4fe7ca49 Fix missing import. 2021-11-04 11:08:15 +00:00
akwizgran
e688448537 Merge branch 'dynamic-tor-ports' into 'master'
Make Tor ports configurable at runtime

See merge request briar/briar!1537
2021-11-04 11:00:09 +00:00
Sebastian Kürten
e0f36ade92 Address review feedback 2021-11-03 16:32:17 +01:00
akwizgran
6517f3f2d0 Bump version numbers for 1.4.1 release. 2021-11-03 15:08:52 +00:00
akwizgran
70d5150faf Fix placeholder in Icelandic translation. 2021-11-03 15:08:05 +00:00
Sebastian Kürten
a1f9e80e12 Inject Tor ports via annotated int parameters 2021-11-03 15:43:01 +01:00
akwizgran
770c87c723 Bump version numbers for 1.4.0 release. 2021-11-03 14:38:32 +00:00
akwizgran
2b5446759f Update translations. 2021-11-03 14:37:34 +00:00
Torsten Grote
edccb9ae14 Merge branch '1081-fix-typo' into 'master'
Fix a typo: without Internet connection -> without an Internet connection

See merge request briar/briar!1540
2021-11-03 14:33:43 +00:00
Torsten Grote
1337fc46b3 Merge branch 'feature-flags-for-hotspot-and-removable-drives' into 'master'
Enable removable drives and sharing app offline in release builds

See merge request briar/briar!1542
2021-11-02 12:09:36 +00:00
akwizgran
ed26dc0b2b Enable removable drives and sharing app offline in release builds. 2021-11-02 11:18:54 +00:00
akwizgran
bf9fe6a146 Fix a typo: without Internet connection -> without an Internet connection. 2021-10-29 13:01:26 +01:00
akwizgran
448ea114f3 Merge branch 'suggest-restarting-hotspot-phone' into 'master'
Add restarting hotspot phone to troubleshooting

See merge request briar/briar!1534
2021-10-29 11:53:52 +00:00
akwizgran
abc523fae3 Merge branch '2011-hotspot-graphic' into 'master'
Add feature graphic for app sharing feature

Closes #2011

See merge request briar/briar!1538
2021-10-29 11:45:21 +00:00
Daniel Lublin
6de5f424b8 Add restarting hotspot phone to troubleshooting 2021-10-21 10:03:48 +02:00
Torsten Grote
703559102a Add feature graphic for app sharing feature 2021-10-19 14:02:57 -03:00
Torsten Grote
4acc5f4d8c Merge branch 'gradle-run-configurations' into 'master'
Convert AS run configurations for tests to Gradle

See merge request briar/briar!1535
2021-10-18 13:31:59 +00:00
Sebastian Kürten
bf091ef854 Use different Tor ports for regular and debug builds 2021-10-16 14:45:14 +02:00
Sebastian Kürten
d4656df384 Make Tor ports configurable at runtime
Instead of using hard-coded values 59050 and 59051 for the Tor socks and
control ports, provide them via a TorPorts interface. This makes it possible
to pass the ports to a TorPortsImpl in modules. Hence it is possible to
configure the Tor port for different types of builds or via command line
options in case of briar headless or other clients using the core code.
2021-10-16 14:41:29 +02:00
akwizgran
e79abeff2e Use a single OkHttpClient instance for all RSS fetches. 2021-09-30 14:09:15 +01:00
akwizgran
4a4d8f4ccf Convert AS run configurations for tests to Gradle. 2021-09-24 14:02:41 +01:00
Torsten Grote
d3dbcfd62d Recreate plausible private group sharing sessions when re-adding contact from group 2021-04-15 14:46:22 -03:00
Torsten Grote
c4c70f5ac2 Do not create PeerSession for groups we created
This needs a CreatorSession which gets created on-demand.
2021-04-15 14:44:58 -03:00
257 changed files with 9088 additions and 1571 deletions

View File

@@ -32,8 +32,9 @@ test:
extends: .base-test
stage: test
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
- ./gradlew -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew -Djava.security.egd=file:/dev/urandom assembleOfficialDebug :briar-headless:linuxJars
- ./gradlew -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
@@ -61,7 +62,7 @@ android test:
when: on_failure
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
when: manual
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- briar-android/**/*
@@ -84,35 +85,42 @@ test_reproducible:
.optional_tests:
stage: optional_tests
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
after_script:
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
extends: .base-test
bridge test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
allow_failure: true
allow_failure: false
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
mailbox integration test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true # TODO figure out how not to allow failure while leaving this optional
script:
# start mailbox
- cd /opt && git clone --depth 1 https://code.briarproject.org/briar/briar-mailbox.git briar-mailbox
- cd briar-mailbox
- mkdir -p /root/.local/share # create directory that mailbox (currently) expects to exist
- ./gradlew run --args="--debug --setup-token 54686973206973206120736574757020746f6b656e20666f722042726961722e" &
# run mailbox integration test once mailbox has started
- cd "$CI_PROJECT_DIR"
- bramble-core/src/test/bash/wait-for-mailbox.sh
- OPTIONAL_TESTS=org.briarproject.bramble.mailbox.MailboxIntegrationTest ./gradlew --info bramble-core:test --tests MailboxIntegrationTest
pre_release_tests:
extends: .optional_tests
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
only:
- tags

View File

@@ -1,21 +1,32 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-android" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/briar-android" />
<configuration default="false" name="All tests" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":briar-android:testOfficialDebugUnitTest" />
<option value=":briar-android:testScreenshotDebugUnitTest" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="GradleRunConfiguration" />
</method>
</configuration>
</component>

View File

@@ -1,14 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-android" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-android" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-android" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in bramble-android" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":bramble-android:testDebugUnitTest" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-api" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in bramble-api" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":bramble-api:animalSnifferMain" />
<option value=":bramble-api:animalSnifferTest" />
<option value=":bramble-api:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-core" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in bramble-core" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":bramble-core:animalSnifferMain" />
<option value=":bramble-core:animalSnifferTest" />
<option value=":bramble-core:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,15 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-java" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-java" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-java" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in bramble-java" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":bramble-java:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,14 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-android" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in briar-android" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":briar-android:testOfficialDebugUnitTest" />
<option value=":briar-android:testScreenshotDebugUnitTest" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-api" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-api" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-api" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in briar-api" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":briar-api:animalSnifferMain" />
<option value=":briar-api:animalSnifferTest" />
<option value=":briar-api:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-core" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in briar-core" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":briar-core:animalSnifferMain" />
<option value=":briar-core:animalSnifferTest" />
<option value=":briar-core:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,15 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-headless" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-headless" />
<option name="PACKAGE_NAME" value="org.briarproject.briar.headless" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/briar-headless" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="All tests in briar-headless" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":briar-headless:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,24 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
<module name="briar.bramble-java" />
<useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
<envs>
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</envs>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<configuration default="false" name="BridgeTest" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--tests &quot;org.briarproject.bramble.plugin.tor.BridgeTest&quot;" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":bramble-java:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>

View File

@@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="H2 Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-core" />
<option name="PACKAGE_NAME" value="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.H2DatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="HyperSQL Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-core" />
<option name="PACKAGE_NAME" value="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.HyperSqlDatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -22,6 +22,15 @@ our site.
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
## Reproducible builds
We provide [docker images](https://code.briarproject.org/briar/briar-reproducer#briar-reproducer)
to ease the task of verifying that the published APK binaries
include nothing but our publicly available source code.
You can either use those images or use them as a blueprint to build your own environment
for reproduction.
## Donate
[![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate) [![Flattr this](https://briarproject.org/img/flattr-badge-large.png "Flattr this")](https://flattr.com/t/592836/)

View File

@@ -15,8 +15,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10308
versionName "1.3.8"
versionCode 10405
versionName "1.4.5"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -42,8 +42,8 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.15'
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
tor "org.briarproject:tor-android:$tor_version"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version@zip"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -53,7 +53,7 @@ dependencies {
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
}
def torBinariesDir = 'src/main/res/raw'

View File

@@ -1,6 +1,8 @@
-keep,includedescriptorclasses class org.briarproject.** { *; }
-keep class org.h2.** { *; }
# Keep the H2 classes that are loaded via reflection
-keep class org.h2.Driver { *; }
-keep class org.h2.engine.Engine { *; }
-keep class org.h2.store.fs.** { *; }
# Don't warn about unused dependencies of H2 classes
-dontwarn org.h2.**
-dontnote org.h2.**
@@ -15,5 +17,4 @@
-dontwarn sun.misc.Unsafe
-dontnote com.google.common.**
# UPnP library isn't used
-dontwarn org.bitlet.weupnp.**
-dontwarn com.fasterxml.jackson.databind.ext.Java7SupportImpl

View File

@@ -70,12 +70,14 @@ class AndroidTorPlugin extends TorPlugin {
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory) {
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture, maxLatency,
maxIdleTime, torDirectory);
maxIdleTime, torDirectory, torSocksPort, torControlPort);
this.app = app;
wakeLock = wakeLockManager.createWakeLock("TorPlugin");
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
import android.app.Application;
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;
@@ -11,7 +12,9 @@ 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;
@@ -56,6 +59,9 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
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,
@@ -71,7 +77,10 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
BatteryManager batteryManager,
AndroidWakeLockManager wakeLockManager,
Clock clock,
@TorDirectory File torDirectory) {
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort,
CryptoComponent crypto) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.app = app;
@@ -86,6 +95,9 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
this.wakeLockManager = wakeLockManager;
this.clock = clock;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
this.crypto = crypto;
}
@Override
@@ -127,13 +139,15 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto);
AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor,
wakefulIoExecutor, app, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, wakeLockManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory);
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,7 +44,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
private AndroidAccountManager accountManager;
public AndroidAccountManagerTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
app = context.mock(Application.class);
applicationInfo = new ApplicationInfo();
applicationInfo.dataDir = testDir.getAbsolutePath();

View File

@@ -1,50 +1,50 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.android.tools.analytics-library:protos:27.1.3:protos-27.1.3.jar:0d9e6cff60b318baac250b6f5bb076a8161103338bf2749cdf1db8a5a13a1f12',
'com.android.tools.analytics-library:shared:27.1.3:shared-27.1.3.jar:10d2a51d8f89ff4ac849888e5a9c60b10e879c30d78545ec1da4d3df7bd56ae4',
'com.android.tools.analytics-library:tracker:27.1.3:tracker-27.1.3.jar:589b355a2ba796cbc0a2b2295737de6661f078262e5f87cd6f540b8d011e5ebb',
'com.android.tools.analytics-library:protos:30.0.3:protos-30.0.3.jar:f62b89dcd9de719c6a7b7e15fb1dd20e45b57222e675cf633607bd0ed6bca7e7',
'com.android.tools.analytics-library:shared:30.0.3:shared-30.0.3.jar:05aa9ba3cc890354108521fdf99802565aae5dd6ca44a6ac8bb8d594d1c1cd15',
'com.android.tools.analytics-library:tracker:30.0.3:tracker-30.0.3.jar:5d0ef35bf6733e96210b5085a2a202152921bf834d345959dce1ca3369b528df',
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
'com.android.tools.build:apksig:4.1.3:apksig-4.1.3.jar:a851980c678ff7a6785388b9a9e8cc094788ce3c4a985ad2b19c2028fd3c631a',
'com.android.tools.build:apkzlib:4.1.3:apkzlib-4.1.3.jar:475903065e7e83a8c1ba78d267c97a54dc5a04d768b535093850423d7b11f2c8',
'com.android.tools.build:builder-model:4.1.3:builder-model-4.1.3.jar:2624a1436c3ab39dd91d3ecf9409a594b0f89ea5cab255f2e9ff11f5ee03d274',
'com.android.tools.build:builder-test-api:4.1.3:builder-test-api-4.1.3.jar:3d2af66726b06b53b8d6d497efcee39ff9f77eb2f8d2cce38b31502383a40d2c',
'com.android.tools.build:builder:4.1.3:builder-4.1.3.jar:a40426cd6d68f6a722ef4950058c075e4547025e8c2fd78e732ad89f15176f84',
'com.android.tools.build:gradle-api:4.1.3:gradle-api-4.1.3.jar:11b1fb9de658bdcf9290b1c1517060d0c4d93f2b27975934989ca4ac890bc077',
'com.android.tools.build:manifest-merger:27.1.3:manifest-merger-27.1.3.jar:ce8d4009b1f1584777a7ffa1da3b0551dc316bc8e08112e442c352af70f46f2d',
'com.android.tools.ddms:ddmlib:27.1.3:ddmlib-27.1.3.jar:8f76e8236d2b9eebf26378746dad025c4c7c056a02e133dae4ddef47b283c710',
'com.android.tools.external.com-intellij:intellij-core:27.1.3:intellij-core-27.1.3.jar:652814fa099b4746fb6f10e19718e476952e8b5bac24e17d914f90650ad21808',
'com.android.tools.external.com-intellij:kotlin-compiler:27.1.3:kotlin-compiler-27.1.3.jar:8d7a78d5efd213c5e467e42bd205582aad73ffc77ee5dc18eb1361c9af72f125',
'com.android.tools.external.org-jetbrains:uast:27.1.3:uast-27.1.3.jar:aea53944a1ac6a05f12297b55290e8cbecfe54c4166260cfba4405823bfe1c78',
'com.android.tools.layoutlib:layoutlib-api:27.1.3:layoutlib-api-27.1.3.jar:23875ce0a8429f33a4e86cc358f658faa0ba9c576f5f05760e544b453d67d04b',
'com.android.tools.lint:lint-api:27.1.3:lint-api-27.1.3.jar:97666be32bcadacd944416ea334a9575ef8f4ad0c8f333151491ff4a7df43e1c',
'com.android.tools.lint:lint-checks:27.1.3:lint-checks-27.1.3.jar:b2d71ae84a31490fe9ff26c706163fe245b2aea98e3eb747214c1085df444708',
'com.android.tools.lint:lint-gradle-api:27.1.3:lint-gradle-api-27.1.3.jar:e54131c287a2954e6ed78a3351e5e10e35a1da2f09ac443bf44b705c71b63a4d',
'com.android.tools.lint:lint-gradle:27.1.3:lint-gradle-27.1.3.jar:6a79e48943649d63665db7b17dbaff7af93e94ab9b15072f1a4d90486294ee9f',
'com.android.tools.lint:lint-model:27.1.3:lint-model-27.1.3.jar:acb9e792db7000e38e3c3ca21a9b14f2de6549d7a3fc92a97ffba3d06345e5bf',
'com.android.tools.lint:lint:27.1.3:lint-27.1.3.jar:5a2e69d0901a3a476a5b2d5001de755868113145f5f6aa557750cfad5389a44b',
'com.android.tools:annotations:27.1.3:annotations-27.1.3.jar:904dd771883496d5dfc86619ab2555968ea4e8a29d7a5f4f7cae6fbf5429f8f5',
'com.android.tools:common:27.1.3:common-27.1.3.jar:17ab4728e3ea50f047dd5937f0faf35f2c5416962ed74891057087ddc328bf96',
'com.android.tools:dvlib:27.1.3:dvlib-27.1.3.jar:cead1c0c356cbe43e6855b0330fe09ef4bec2c72e22bdb4c6e7cf7e6b1dfbc37',
'com.android.tools:repository:27.1.3:repository-27.1.3.jar:99de1a178855b56b8cd91a56296f1e0a9399c445e6acc51f1d2927947cc472cb',
'com.android.tools:sdk-common:27.1.3:sdk-common-27.1.3.jar:b591e2aa0f1be600795f5c9e2bf81cba9b052bee452fc86c3362b5dd9e427a14',
'com.android.tools:sdklib:27.1.3:sdklib-27.1.3.jar:ad6c08a45fe2904d05656bdddf9f623fa5c1d16bbd7b8d6a270a0734136ae02e',
'com.android:signflinger:4.1.3:signflinger-4.1.3.jar:f3103b55ccdc8dd9ee2517eb26af93b904d41303726594372d0df59d51156e5c',
'com.android:zipflinger:4.1.3:zipflinger-4.1.3.jar:48569896c0497268308a8014c66eb0f2bace2b9e2fc9390f3012823fb86387d5',
'com.android.tools.build:apksig:7.0.3:apksig-7.0.3.jar:012337a2803c9a30dfc41dcbc6450686ee9e5f582549f7f126479f743a343ec9',
'com.android.tools.build:apkzlib:7.0.3:apkzlib-7.0.3.jar:b31e53174c92db83c5cc6e7dac6734ea4e907a72e452c2bf1818dfd082c59397',
'com.android.tools.build:builder-model:7.0.3:builder-model-7.0.3.jar:483f99d7494a5bed027e1e8d29111384cf535d4842f0be5a79805bd44bb68d4e',
'com.android.tools.build:builder-test-api:7.0.3:builder-test-api-7.0.3.jar:f6de4bc2cef545e8367bf82d7c733829c7be3b0b3b8b09fd8c58f2150e59ab46',
'com.android.tools.build:builder:7.0.3:builder-7.0.3.jar:c6952da0094b094c2ba0fe84c675622097c5d9b9f9beb53485b860320540cf1d',
'com.android.tools.build:manifest-merger:30.0.3:manifest-merger-30.0.3.jar:72b346ba6318b4b6260e6e49df4bea5da2e12329ab6c2beb2269c49a9f51f178',
'com.android.tools.ddms:ddmlib:30.0.3:ddmlib-30.0.3.jar:7a914a68ab93393657297234e2f37b22410ae9a433cba692ce8c727c9607e3bb',
'com.android.tools.external.com-intellij:intellij-core:30.0.3:intellij-core-30.0.3.jar:1ebe858d3f58eeaa8c06507f8ac0f1c7051e6c61f35a70f3c3967d5734d3abc5',
'com.android.tools.external.com-intellij:kotlin-compiler:30.0.3:kotlin-compiler-30.0.3.jar:ed00e441f427cb4e0d418287b9da30b12b7f735f9af32e6b5d3dc960b6a742fc',
'com.android.tools.external.org-jetbrains:uast:30.0.3:uast-30.0.3.jar:a77801bee6ff509910e459525c9c34d7f04b066ade123547f16f1917548eadea',
'com.android.tools.layoutlib:layoutlib-api:30.0.3:layoutlib-api-30.0.3.jar:4caa87e9ca2e11315f650d576cd59fec1793373bc3fca3f6d53c029e7534e7c4',
'com.android.tools.lint:lint-api:30.0.3:lint-api-30.0.3.jar:bcecbd2f752a6560096a9029a47d1de6bd788a51bab505c5ebfba6a18524b983',
'com.android.tools.lint:lint-checks:30.0.3:lint-checks-30.0.3.jar:25a7cd42dc3ad502337f131fb8b7e873c53301db0a67b1c64dd4ae7a8eb66cec',
'com.android.tools.lint:lint-gradle:30.0.3:lint-gradle-30.0.3.jar:94544d6147a809bf2fd3440e51f28a4e42e547d74aab53eefd74938cdad42c26',
'com.android.tools.lint:lint-model:30.0.3:lint-model-30.0.3.jar:0b940a7f575c2ff5cbd038260f41dde686a93c672213881ead3ce8af3513b396',
'com.android.tools.lint:lint:30.0.3:lint-30.0.3.jar:ee4f11001e0c7e3b776e0d67399ad354b19b0f168822ec2b7db47c0910ed227d',
'com.android.tools:annotations:30.0.3:annotations-30.0.3.jar:5c1944982fda8555855c4f5422fabf0dc8e2306e1f5460e9ad82dae71316bc31',
'com.android.tools:common:30.0.3:common-30.0.3.jar:8751efaaf2c2ddd1f0a37526c794347def6a3057ca9fc510307c13a6cf0d036f',
'com.android.tools:dvlib:30.0.3:dvlib-30.0.3.jar:5affafcec390041e5afd64cb924153f5e474db47ee8ccc2f555b495083141233',
'com.android.tools:repository:30.0.3:repository-30.0.3.jar:0a40c6f16c506903ce2c609affd8228aceda73a69d93dfa42d4f02b8491449f6',
'com.android.tools:sdk-common:30.0.3:sdk-common-30.0.3.jar:b45570a380360236ffee0f6bb593d66b673bad3834dfe0d6c9871fa7188ee0eb',
'com.android.tools:sdklib:30.0.3:sdklib-30.0.3.jar:7088f20a414fab170a21e457825e14ebe099f753558e02c8acc12c67eb412162',
'com.android:signflinger:7.0.3:signflinger-7.0.3.jar:903a4536db3e96b4e1e1dc1e400eb0b91bf7866d9b39cd7ec94d75dde158f152',
'com.android:zipflinger:7.0.3:zipflinger-7.0.3.jar:fd209c960a3eff7a339e6fcba07d5e9ef4604d1633c69ab2df987460d9804140',
'com.beust:jcommander:1.78:jcommander-1.78.jar:7891debb84b5f83e9bd57593ebece3399abbe0fd938cf306b3534c57913b9615',
'com.github.javaparser:javaparser-core:3.17.0:javaparser-core-3.17.0.jar:23f5c982e1c7771423d37d52c774e8d2e80fd7ea7305ebe448797a96f67e6fca',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.code.gson:gson:2.8.6:gson-2.8.6.jar:c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f',
'com.google.dagger:dagger-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
'com.google.dagger:dagger-producers:2.33:dagger-producers-2.33.jar:5897f0b6eef799c2adfe3ccacc58c0fb374d58acb063c3ebe5366c38a8bce5c8',
'com.google.dagger:dagger-spi:2.33:dagger-spi-2.33.jar:e2dcab2221b8afb9556ef0a1c83b0bd5f42552e254322a257330f754cdbbb9d4',
'com.google.dagger:dagger:2.33:dagger-2.33.jar:d8798c5b8cf6b125234e33af5c6293bb9f2208ce29b57924c35b8c0be7b6bdcb',
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d',
'com.google.errorprone:error_prone_annotations:2.3.4:error_prone_annotations-2.3.4.jar:baf7d6ea97ce606c53e11b6854ba5f2ce7ef5c24dddf0afa18d1260bd25b002c',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
'com.google.guava:guava:28.1-jre:guava-28.1-jre.jar:30beb8b8527bd07c6e747e77f1a92122c2f29d57ce347461a4a55eb26e382da4',
'com.google.guava:guava:30.1-jre:guava-30.1-jre.jar:e6dd072f9d3fe02a4600688380bd422bdac184caf6fe2418cfdd0934f09432aa',
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
@@ -54,63 +54,108 @@ dependencyVerification {
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:3.0.7:istack-commons-runtime-3.0.7.jar:6443e10ba2e259fb821d9b6becf10db5316285fc30c53cec9d7b19a3877e7fdf',
'com.sun.xml.fastinfoset:FastInfoset:1.2.15:FastInfoset-1.2.15.jar:785861db11ca1bd0d1956682b974ad73eb19cd3e01a4b3fa82d62eca97210aec',
'com.sun.istack:istack-commons-runtime:3.0.8:istack-commons-runtime-3.0.8.jar:4ffabb06be454a05e4398e20c77fa2b6308d4b88dfbef7ca30a76b5b7d5505ef',
'com.sun.xml.fastinfoset:FastInfoset:1.2.16:FastInfoset-1.2.16.jar:056f3a1e144409f21ed16afc26805f58e9a21f3fce1543c42d400719d250c511',
'com.thoughtworks.qdox:qdox:1.12.1:qdox-1.12.1.jar:21fba22f830e9268f07cf4ab2d99e8181abbdcb0cb91ee0228eb3cb918dcdd1d',
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-io:commons-io:2.4:commons-io-2.4.jar:cc6a41dc3eaacc9e440a6bd0d2890b20d36b4ee408fe2d67122f328bb6e01581',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
'javax.activation:javax.activation-api:1.2.0:javax.activation-api-1.2.0.jar:43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393',
'info.picocli:picocli:4.5.2:picocli-4.5.2.jar:b4395e9a67932616efd2245d984bf5fcd453c2c5049558c3ce959ac2af4d3fac',
'it.unimi.dsi:fastutil:8.4.0:fastutil-8.4.0.jar:2ad2824a4a0a0eb836b52ee2fc84ba2134f44bce7bfa54015ae3f31c710a3071',
'jakarta.activation:jakarta.activation-api:1.2.1:jakarta.activation-api-1.2.1.jar:8b0a0f52fa8b05c5431921a063ed866efaa41dadf2e3a7ee3e1961f2b0d9645b',
'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2:jakarta.xml.bind-api-2.3.2.jar:69156304079bdeed9fc0ae3b39389f19b3cc4ba4443bc80508995394ead742ea',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
'jline:jline:2.14.6:jline-2.14.6.jar:97d1acaac82409be42e622d7a54d3ae9d08517e8aefdea3d2ba9791150c2f02d',
'junit:junit:4.13.1:junit-4.13.1.jar:c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.java.dev.jna:jna-platform:5.6.0:jna-platform-5.6.0.jar:9ecea8bf2b1b39963939d18b70464eef60c508fed8820f9dcaba0c35518eabf7',
'net.java.dev.jna:jna:5.6.0:jna-5.6.0.jar:5557e235a8aa2f9766d5dc609d67948f2a8832c2d796cea9ef1d6cbe0b3b7eaf',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.ant:ant-antlr:1.10.9:ant-antlr-1.10.9.jar:7623dc9d0f20ea713290c6bf1a23f4c059447aef7ff9f5b2be75960f3f028d2e',
'org.apache.ant:ant-junit:1.10.9:ant-junit-1.10.9.jar:960bdc8827954d62206ba42d0a68a7ee4476175ba47bb113e17e77cce7394630',
'org.apache.ant:ant-launcher:1.10.9:ant-launcher-1.10.9.jar:fcce891f57f3be72149ff96ac2a80574165b3e0839866b95d24528f3027d50c1',
'org.apache.ant:ant:1.10.9:ant-1.10.9.jar:0715478af585ea80a18985613ebecdc7922122d45b2c3c970ff9b352cddb75fc',
'org.apache.commons:commons-compress:1.20:commons-compress-1.20.jar:0aeb625c948c697ea7b205156e112363b59ed5e2551212cd4e460bdb72c7c06e',
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents: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.15:tor-android-0.3.5.15.jar:560c5070166300b396cb2f28d82d9f639ee1fb5479096a3cef67da56d39937ad',
'org.briarproject:tor-android:0.3.5.17:tor-android-0.3.5.17.jar:1888afc10a26b93d00a010ea27bf0b1b162a6d524688b08b98d70d14dc363b54',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
'org.checkerframework:checker-qual:3.5.0:checker-qual-3.5.0.jar:729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4',
'org.codehaus.groovy:groovy-ant:3.0.7:groovy-ant-3.0.7.jar:6ed2ba82813d128f7050c24142e87b3dc2ad8b504786280eb03e81f0cf6a5793',
'org.codehaus.groovy:groovy-astbuilder:3.0.7:groovy-astbuilder-3.0.7.jar:b290451eb1583666e906c41f7d14747b4cc96363c99c478b244634fd5dfc9013',
'org.codehaus.groovy:groovy-cli-picocli:3.0.7:groovy-cli-picocli-3.0.7.jar:71b4bd11fb30a9c7b5618e22122c9c5141958fb27f4dcf0068b6f715088f6916',
'org.codehaus.groovy:groovy-console:3.0.7:groovy-console-3.0.7.jar:0541b358b6b8e5363215026736168fccfec1d91bac678d066fa77349eeeaa5dd',
'org.codehaus.groovy:groovy-datetime:3.0.7:groovy-datetime-3.0.7.jar:b9823d14b1a4f94236ae2f8a471701aab17e093e1b33402b91550b5c8dd88f04',
'org.codehaus.groovy:groovy-docgenerator:3.0.7:groovy-docgenerator-3.0.7.jar:bf53f7a11c9eb1e278e1b8ed2714c741bcf781235c803ad3ba1555f2614573f3',
'org.codehaus.groovy:groovy-groovydoc:3.0.7:groovy-groovydoc-3.0.7.jar:86b24dfc23c005066ab83927cdb54177f06c9531773f2e2d2ecc9a131f7c2677',
'org.codehaus.groovy:groovy-groovysh:3.0.7:groovy-groovysh-3.0.7.jar:5c40e78cbc09726aedd1c75fab112d245d665d6294870f9119e6cd3013ed14ab',
'org.codehaus.groovy:groovy-jmx:3.0.7:groovy-jmx-3.0.7.jar:0a89f3007884eb156751937d93382038b83d39c7c2f0ab156ebf251a7251f2ab',
'org.codehaus.groovy:groovy-json:3.0.7:groovy-json-3.0.7.jar:df1f0ee475e3fc93a6a0d17548294e160cca5de6d9d36817a7be1fbe650de03b',
'org.codehaus.groovy:groovy-jsr223:3.0.7:groovy-jsr223-3.0.7.jar:1dbd969595332416193baa660fbb45743d19696eaa25fe98e591a2739e13517e',
'org.codehaus.groovy:groovy-macro:3.0.7:groovy-macro-3.0.7.jar:c6cc06df526b39e2c359e2435f0071594c5a1c7babafaa6c184fdd8fa931531f',
'org.codehaus.groovy:groovy-nio:3.0.7:groovy-nio-3.0.7.jar:db54c577882b294cd8c975ec5451596441baf54781319c61627dca0e0c2361ef',
'org.codehaus.groovy:groovy-servlet:3.0.7:groovy-servlet-3.0.7.jar:5b6a909bf501c209adfb6205b9e740649609074455fd979bf9da4853e6ff9a39',
'org.codehaus.groovy:groovy-sql:3.0.7:groovy-sql-3.0.7.jar:252bb6c74e1a9f41756ad4fbd3b0d2eddc93bb61109961dd1952a37bf2d57a64',
'org.codehaus.groovy:groovy-swing:3.0.7:groovy-swing-3.0.7.jar:bd942032d9328d54c6679c49a41f6caa0d4a0039ebe598493b8a647730d98cff',
'org.codehaus.groovy:groovy-templates:3.0.7:groovy-templates-3.0.7.jar:f119e07f650ef186ae5a4b944f9e30915b14311bad47c94a6b32de8d4f69bc80',
'org.codehaus.groovy:groovy-test-junit5:3.0.7:groovy-test-junit5-3.0.7.jar:c16eeea07b8e396891e266d7ba9388b24ac804237ffdd9a792b0d08969bad014',
'org.codehaus.groovy:groovy-test:3.0.7:groovy-test-3.0.7.jar:f71afd7c25d43017f89ea47e6de6daec971d159047dae083c1513a8422d44b90',
'org.codehaus.groovy:groovy-testng:3.0.7:groovy-testng-3.0.7.jar:713d5f2231bbb5712aefd362151b9ffd884aeb7ef2e773315cc54259cbdd063d',
'org.codehaus.groovy:groovy-xml:3.0.7:groovy-xml-3.0.7.jar:8a62e7c9ddece3e82676c4bef2f2c100f459602cd1fb6a14e94187bf863e97ff',
'org.codehaus.groovy:groovy:3.0.7:groovy-3.0.7.jar:51d1777e8dd1f00e60ea56e00d8a354ff5aab1f00fc8464ae8d39d71867e401f',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f',
'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60',
'org.glassfish.jaxb:jaxb-runtime:2.3.2:jaxb-runtime-2.3.2.jar:e6e0a1e89fb6ff786279e6a0082d5cef52dc2ebe67053d041800737652b4fd1b',
'org.glassfish.jaxb:txw2:2.3.2:txw2-2.3.2.jar:4a6a9f483388d461b81aa9a28c685b8b74c0597993bf1884b04eddbca95f48fe',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a',
'org.jacoco:org.jacoco.agent:0.8.3:org.jacoco.agent-0.8.3.jar:522deb254ee16a04cc8341cc8f335f5cb7232982994d961b9cf3a0454709209f',
'org.jacoco:org.jacoco.ant:0.8.3:org.jacoco.ant-0.8.3.jar:735844e1ae15f9b875b42a27ac5cb61cc26e106d9e839e5d1c6756709b424ce0',
'org.jacoco:org.jacoco.core:0.8.3:org.jacoco.core-0.8.3.jar:0818437bc060a0c7cc798148f22b713702aae2771aba104444407697d578f1ea',
'org.jacoco:org.jacoco.report:0.8.3:org.jacoco.report-0.8.3.jar:aae08fa4ff043c807b8876cdb2d8705eb8449a55efce461baa6c09da245088c1',
'org.jetbrains.intellij.deps:trove4j:1.0.20181211:trove4j-1.0.20181211.jar:affb7c85a3c87bdcf69ff1dbb84de11f63dc931293934bc08cd7ab18de083601',
'org.jetbrains.kotlin:kotlin-reflect:1.4.32:kotlin-reflect-1.4.32.jar:dbf19e9cdaa9c3c170f3f6f6ce3922f38dfc1d7fa1cab5b7c23a19da8b5eec5b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72:kotlin-stdlib-jdk8-1.3.72.jar:133da70cfc07b56094282eac5c59bccd59f167ee2ead22e5282876d8bc10bf95',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32:kotlin-stdlib-common-1.4.32.jar:e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32:kotlin-stdlib-jdk7-1.4.32.jar:5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32:kotlin-stdlib-jdk8-1.4.32.jar:adc43e54757b106e0cd7b3b7aa257dff471b61efdabe067fc02b2f57e2396262',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.32:kotlin-stdlib-1.4.32.jar:13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
'org.junit.jupiter:junit-jupiter-api:5.7.0:junit-jupiter-api-5.7.0.jar:b03f78e0daeed2d77a0af9bcd662b4cdb9693f7ee72e01a539b508b84c63d182',
'org.junit.jupiter:junit-jupiter-engine:5.7.0:junit-jupiter-engine-5.7.0.jar:dfa26af94644ac2612dde6625852fcb550a0d21caa243257de54cba738ba87af',
'org.junit.platform:junit-platform-commons:1.7.0:junit-platform-commons-1.7.0.jar:5330ee87cc7586e6e25175a34e9251624ff12ff525269d3415d0b4ca519b6fea',
'org.junit.platform:junit-platform-engine:1.7.0:junit-platform-engine-1.7.0.jar:75f21a20dc594afdc875736725b408cec6d0344874d29f34b2dd3075500236f2',
'org.junit.platform:junit-platform-launcher:1.7.0:junit-platform-launcher-1.7.0.jar:fbdc748fde4c4279fe1d3c607447cb3b7ccd45d7338fc574f8a894ddf2d16818',
'org.jvnet.staxex:stax-ex:1.8.1:stax-ex-1.8.1.jar:20522549056e9e50aa35ef0b445a2e47a53d06be0b0a9467d704e2483ffb049a',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.opentest4j:opentest4j:1.2.0:opentest4j-1.2.0.jar:58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2',
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.testng:testng:7.3.0:testng-7.3.0.jar:63727488f9717d57f0d0a0fee5a1fc10a2be9cfcff2ec3a7187656d663c0774e',
'xerces:xercesImpl:2.12.0:xercesImpl-2.12.0.jar:b50d3a4ca502faa4d1c838acb8aa9480446953421f7327e338c5dda3da5e76d0',
'xml-apis:xml-apis:1.4.01:xml-apis-1.4.01.jar:a840968176645684bb01aed376e067ab39614885f9eee44abe35a5f20ebe7fad',
]
}

View File

@@ -9,11 +9,11 @@ apply from: 'witness.gradle'
dependencies {
implementation "com.google.dagger:dagger:$dagger_version"
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
}

View File

@@ -11,7 +11,11 @@ public interface FeatureFlags {
boolean shouldEnableDisappearingMessages();
boolean shouldEnableTransferData();
boolean shouldEnableMailbox();
boolean shouldEnableShareAppViaOfflineHotspot();
boolean shouldEnablePrivateGroupsInCore();
boolean shouldEnableForumsInCore();
boolean shouldEnableBlogsInCore();
}

View File

@@ -0,0 +1,35 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.lang.ref.WeakReference;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Provider;
/**
* A {@link Provider} that keeps a {@link WeakReference} to the last provided
* instance and provides the same instance again until the instance is garbage
* collected.
*/
@NotNullByDefault
public abstract class WeakSingletonProvider<T> implements Provider<T> {
private final Object lock = new Object();
@GuardedBy("lock")
private WeakReference<T> ref = new WeakReference<>(null);
@Override
public T get() {
synchronized (lock) {
T instance = ref.get();
if (instance == null) {
instance = createInstance();
ref = new WeakReference<>(instance);
}
return instance;
}
}
public abstract T createInstance();
}

View File

@@ -128,7 +128,7 @@ public interface ClientHelper {
* group.
*/
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException;
throws DbException;
/**
* Stores the given contact ID in the group metadata of the given contact

View File

@@ -107,6 +107,32 @@ public interface ContactManager {
*/
String getHandshakeLink() throws DbException;
/**
* Returns the handshake link that needs to be sent to a contact we want
* to add.
*/
String getHandshakeLink(Transaction txn) throws DbException;
/**
* Creates a {@link PendingContact} from the given handshake link and
* alias, adds it to the database and returns it.
*
* @param link The handshake link received from the pending contact
* @param alias The alias the user has given this pending contact
* @throws UnsupportedVersionException If the link uses a format version
* that is not supported
* @throws FormatException If the link is invalid
* @throws GeneralSecurityException If the pending contact's handshake
* public key is invalid
* @throws ContactExistsException If a contact with the same handshake
* public key already exists
* @throws PendingContactExistsException If a pending contact with the same
* handshake public key already exists
*/
PendingContact addPendingContact(Transaction txn, String link, String alias)
throws DbException, FormatException, GeneralSecurityException,
ContactExistsException, PendingContactExistsException;
/**
* Creates a {@link PendingContact} from the given handshake link and
* alias, adds it to the database and returns it.
@@ -140,11 +166,24 @@ public interface ContactManager {
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
throws DbException;
/**
* Returns a list of {@link PendingContact PendingContacts} and their
* {@link PendingContactState states}.
*/
Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(Transaction txn)
throws DbException;
/**
* Removes a {@link PendingContact}.
*/
void removePendingContact(PendingContactId p) throws DbException;
/**
* Removes a {@link PendingContact}.
*/
void removePendingContact(Transaction txn, PendingContactId p)
throws DbException;
/**
* Returns the contact with the given ID.
*/

View File

@@ -170,4 +170,11 @@ public interface CryptoComponent {
* length. The line terminator is CRLF.
*/
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
*/
String encodeOnionAddress(byte[] publicKey);
}

View File

@@ -471,6 +471,14 @@ public interface DatabaseComponent extends TransactionManager {
Map<MessageId, Integer> 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.
*/
void resetUnackedMessagesToSend(Transaction txn, ContactId c)
throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages

View File

@@ -0,0 +1,8 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class InvalidMailboxIdException extends Exception {
}

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public class MailboxAuthToken extends MailboxId {
public MailboxAuthToken(byte[] id) {
super(id);
}
/**
* Creates a {@link MailboxAuthToken} from the given string.
*
* @throws InvalidMailboxIdException if token is not valid.
*/
public static MailboxAuthToken fromString(@Nullable String token)
throws InvalidMailboxIdException {
return new MailboxAuthToken(bytesFromString(token));
}
}

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public class MailboxFileId extends MailboxId {
public MailboxFileId(byte[] id) {
super(id);
}
/**
* Creates a {@link MailboxFileId} from the given string.
*
* @throws IllegalArgumentException if token is not valid.
*/
public static MailboxFileId fromString(@Nullable String token)
throws InvalidMailboxIdException {
return new MailboxFileId(bytesFromString(token));
}
}

View File

@@ -0,0 +1,24 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
public class MailboxFolderId extends MailboxId {
public MailboxFolderId(byte[] id) {
super(id);
}
/**
* Creates a {@link MailboxFolderId} from the given string.
*
* @throws IllegalArgumentException if token is not valid.
*/
public static MailboxFolderId fromString(@Nullable String token)
throws InvalidMailboxIdException {
return new MailboxFolderId(bytesFromString(token));
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.bramble.api.mailbox;
import com.fasterxml.jackson.annotation.JsonValue;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Locale;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@ThreadSafe
@NotNullByDefault
public abstract class MailboxId extends UniqueId {
MailboxId(byte[] id) {
super(id);
}
/**
* Returns valid {@link MailboxId} bytes from the given string.
*
* @throws InvalidMailboxIdException if token is not valid.
*/
static byte[] bytesFromString(@Nullable String token)
throws InvalidMailboxIdException {
if (token == null || token.length() != 64) {
throw new InvalidMailboxIdException();
}
try {
return fromHexString(token);
} catch (IllegalArgumentException e) {
throw new InvalidMailboxIdException();
}
}
/**
* Returns the string representation expected by the mailbox API.
* Also used for serialization.
*/
@Override
@JsonValue
public String toString() {
return toHexString(getBytes()).toLowerCase(Locale.US);
}
}

View File

@@ -0,0 +1,35 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import javax.annotation.Nullable;
public interface MailboxManager {
/**
* @return true if a mailbox is already paired.
*/
boolean isPaired(Transaction txn) throws DbException;
/**
* @return the current status of the mailbox.
*/
MailboxStatus getMailboxStatus(Transaction txn) throws DbException;
/**
* Returns the currently running pairing task,
* or null if no pairing task is running.
*/
@Nullable
MailboxPairingTask getCurrentPairingTask();
/**
* Starts and returns a pairing task. If a pairing task is already running,
* it will be returned and the argument will be ignored.
*
* @param qrCodePayload The ISO-8859-1 encoded bytes of the mailbox QR code.
*/
MailboxPairingTask startPairingTask(String qrCodePayload);
}

View File

@@ -0,0 +1,59 @@
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

@@ -0,0 +1,21 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface MailboxPairingTask extends Runnable {
/**
* Adds an observer to the task. The observer will be notified on the
* event thread of the current state of the task and any subsequent state
* changes.
*/
void addObserver(Consumer<MailboxPairingState> observer);
/**
* Removes an observer from the task.
*/
void removeObserver(Consumer<MailboxPairingState> observer);
}

View File

@@ -0,0 +1,33 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxProperties {
private final String baseUrl;
private final MailboxAuthToken authToken;
private final boolean owner;
public MailboxProperties(String baseUrl, MailboxAuthToken authToken,
boolean owner) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.owner = owner;
}
public String getBaseUrl() {
return baseUrl;
}
public MailboxAuthToken getAuthToken() {
return authToken;
}
public boolean isOwner() {
return owner;
}
}

View File

@@ -0,0 +1,33 @@
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 javax.annotation.Nullable;
@NotNullByDefault
public interface MailboxSettingsManager {
@Nullable
MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException;
void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException;
MailboxStatus getOwnMailboxStatus(Transaction txn) throws DbException;
void recordSuccessfulConnection(Transaction txn, long now)
throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException;
void setPendingUpload(Transaction txn, ContactId id,
@Nullable String filename) throws DbException;
@Nullable
String getPendingUpload(Transaction txn, ContactId id) throws DbException;
}

View File

@@ -0,0 +1,59 @@
package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class MailboxStatus {
private final long lastAttempt, lastSuccess;
private final int attemptsSinceSuccess;
public MailboxStatus(long lastAttempt, long lastSuccess,
int attemptsSinceSuccess) {
this.lastAttempt = lastAttempt;
this.lastSuccess = lastSuccess;
this.attemptsSinceSuccess = attemptsSinceSuccess;
}
/**
* Returns the time of the last attempt to connect to the mailbox, in
* milliseconds since the Unix epoch, or -1 if no attempt has been made.
* <p>
* If an attempt is in progress and has not yet succeeded or failed then
* this method returns the time of the previous attempt, or -1 if the
* current attempt is the first.
*/
public long getTimeOfLastAttempt() {
return lastAttempt;
}
/**
* Returns the time of the last successful attempt to connect to the
* mailbox, in milliseconds since the Unix epoch, or -1 if no attempt has
* succeeded.
* <p>
* If the last attempt was successful then this method returns the same
* value as {@link #getTimeOfLastAttempt()}. If an attempt is in progress
* and has not yet succeeded or failed then this method returns the time
* of the previous successful connection, or -1 if no attempt has
* succeeded.
*/
public long getTimeOfLastSuccess() {
return lastSuccess;
}
/**
* Returns the number of attempts to connect to the mailbox that have
* failed since the last attempt succeeded, or the number of attempts that
* have been made, if no attempt has ever succeeded.
* <p>
* If an attempt is in progress and has not yet succeeded or failed then
* it is not included in this count.
*/
public int getAttemptsSinceSuccess() {
return attemptsSinceSuccess;
}
}

View File

@@ -1,17 +1,14 @@
package org.briarproject.bramble.api.plugin;
import static java.util.concurrent.TimeUnit.DAYS;
public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor");
// Transport properties
String PROP_ONION_V2 = "onion";
String PROP_ONION_V3 = "onion3";
int SOCKS_PORT = 59050;
int CONTROL_PORT = 59051;
int DEFAULT_SOCKS_PORT = 59050;
int DEFAULT_CONTROL_PORT = 59051;
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
@@ -21,14 +18,7 @@ public interface TorConstants {
String PREF_TOR_PORT = "port";
String PREF_TOR_MOBILE = "useMobileData";
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
String HS_PRIVATE_KEY_V2 = "onionPrivKey";
String HS_PRIVATE_KEY_V3 = "onionPrivKey3";
String HS_V3_CREATED = "onionPrivKey3Created";
/**
* How long to publish a v3 hidden service before retiring the v2 service.
*/
long V3_MIGRATION_PERIOD_MS = DAYS.toMillis(180);
// Values for PREF_TOR_NETWORK
int PREF_TOR_NETWORK_AUTOMATIC = 0;

View File

@@ -0,0 +1,20 @@
package org.briarproject.bramble.api.plugin;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the control port for the Tor plugin.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
public @interface TorControlPort {
}

View File

@@ -0,0 +1,20 @@
package org.briarproject.bramble.api.plugin;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the socks port for the Tor plugin.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
public @interface TorSocksPort {
}

View File

@@ -22,4 +22,11 @@ public interface SettingsManager {
* namespace.
*/
void mergeSettings(Settings s, String namespace) throws DbException;
/**
* Merges the given settings with any existing settings in the given
* namespace.
*/
void mergeSettings(Transaction txn, Settings s, String namespace)
throws DbException;
}

View File

@@ -113,9 +113,25 @@ public interface KeyManager {
/**
* Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was
* unexpected.
* unexpected. Marks the tag as recognised and updates the reordering
* window.
*/
@Nullable
StreamContext getStreamContext(TransportId t, byte[] tag)
throws DbException;
/**
* Looks up the given tag and returns a {@link StreamContext} for reading
* from the corresponding stream, or null if an error occurs or the tag was
* unexpected. Only returns the StreamContext; does not mark the tag as
* recognised.
*/
@Nullable
StreamContext getStreamContextOnly(TransportId t, byte[] tag)
throws DbException;
/**
* Marks the tag as recognised and updates the reordering window.
*/
void markTagAsRecognised(TransportId t, byte[] tag) throws DbException;
}

View File

@@ -1,12 +1,17 @@
package org.briarproject.bramble.test;
import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.After;
public abstract class BrambleMockTestCase extends BrambleTestCase {
protected final Mockery context = new Mockery();
public BrambleMockTestCase() {
context.setThreadingPolicy(new Synchroniser());
}
@After
public void checkExpectations() {
context.assertIsSatisfied();

View File

@@ -25,7 +25,11 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -45,6 +49,7 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
public class TestUtils {
@@ -209,6 +214,24 @@ public class TestUtils {
getAgreementPublicKey(), verified);
}
public static void writeBytes(File file, byte[] bytes)
throws IOException {
FileOutputStream outputStream = new FileOutputStream(file);
//noinspection TryFinallyCanBeTryWithResources
try {
outputStream.write(bytes);
} finally {
outputStream.close();
}
}
public static byte[] readBytes(File file) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
FileInputStream inputStream = new FileInputStream(file);
copyAndClose(inputStream, outputStream);
return outputStream.toByteArray();
}
public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size();
if (size == 0) throw new IllegalArgumentException();

View File

@@ -10,13 +10,18 @@ apply from: '../dagger.gradle'
dependencies {
implementation project(path: ':bramble-api', configuration: 'default')
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
implementation 'org.bouncycastle:bcprov-jdk15to18:1.70'
//noinspection GradleDependency
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
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'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
@@ -25,7 +30,8 @@ dependencies {
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.record.RecordModule;
@@ -43,6 +44,7 @@ import dagger.Module;
IoModule.class,
KeyAgreementModule.class,
LifecycleModule.class,
MailboxModule.class,
PluginModule.class,
PropertiesModule.class,
RecordModule.class,

View File

@@ -121,28 +121,40 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override
public String getHandshakeLink() throws DbException {
KeyPair keyPair = db.transactionWithResult(true,
identityManager::getHandshakeKeys);
return db.transactionWithResult(true, this::getHandshakeLink);
}
@Override
public String getHandshakeLink(Transaction txn) throws DbException {
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
return pendingContactFactory.createHandshakeLink(keyPair.getPublic());
}
@Override
public PendingContact addPendingContact(Transaction txn, String link,
String alias)
throws DbException, FormatException, GeneralSecurityException {
PendingContact p =
pendingContactFactory.createPendingContact(link, alias);
AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair);
return p;
}
@Override
public PendingContact addPendingContact(String link, String alias)
throws DbException, FormatException, GeneralSecurityException {
PendingContact p =
pendingContactFactory.createPendingContact(link, alias);
Transaction txn = db.startTransaction(false);
try {
AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair);
PendingContact p = addPendingContact(txn, link, alias);
db.commitTransaction(txn);
return p;
} finally {
db.endTransaction(txn);
}
return p;
}
@Override
@@ -154,8 +166,14 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts()
throws DbException {
Collection<PendingContact> pendingContacts =
db.transactionWithResult(true, db::getPendingContacts);
return db.transactionWithResult(true, this::getPendingContacts);
}
@Override
public Collection<Pair<PendingContact, PendingContactState>> getPendingContacts(
Transaction txn)
throws DbException {
Collection<PendingContact> pendingContacts = db.getPendingContacts(txn);
List<Pair<PendingContact, PendingContactState>> pairs =
new ArrayList<>(pendingContacts.size());
for (PendingContact p : pendingContacts) {
@@ -168,7 +186,13 @@ class ContactManagerImpl implements ContactManager, EventListener {
@Override
public void removePendingContact(PendingContactId p) throws DbException {
db.transaction(false, txn -> db.removePendingContact(txn, p));
db.transaction(false, txn -> removePendingContact(txn, p));
}
@Override
public void removePendingContact(Transaction txn, PendingContactId p)
throws DbException {
db.removePendingContact(txn, p);
states.remove(p);
}

View File

@@ -7,6 +7,7 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
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.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent;
@@ -21,11 +22,13 @@ import org.briarproject.bramble.api.crypto.SignaturePrivateKey;
import org.briarproject.bramble.api.crypto.SignaturePublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.Base32;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
@@ -58,6 +61,8 @@ class CryptoComponentImpl implements CryptoComponent {
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
private static final byte PBKDF_FORMAT_SCRYPT = 0;
private static final byte PBKDF_FORMAT_SCRYPT_STRENGTHENED = 1;
private static final byte ONION_HS_PROTOCOL_VERSION = 3;
private static final int ONION_CHECKSUM_BYTES = 2;
private final SecureRandom secureRandom;
private final PasswordBasedKdf passwordBasedKdf;
@@ -442,4 +447,21 @@ class CryptoComponentImpl implements CryptoComponent {
public String asciiArmour(byte[] b, int lineLength) {
return AsciiArmour.wrap(b, lineLength);
}
@Override
public String encodeOnionAddress(byte[] publicKey) {
Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
digest.update(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length);
digest.update(ONION_HS_PROTOCOL_VERSION);
byte[] checksum = new byte[digest.getDigestSize()];
digest.doFinal(checksum, 0);
byte[] address = new byte[publicKey.length + ONION_CHECKSUM_BYTES + 1];
arraycopy(publicKey, 0, address, 0, publicKey.length);
arraycopy(checksum, 0, address, publicKey.length, ONION_CHECKSUM_BYTES);
address[address.length - 1] = ONION_HS_PROTOCOL_VERSION;
return Base32.encode(address).toLowerCase();
}
}

View File

@@ -757,6 +757,13 @@ 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.
*/
void resetUnackedMessagesToSend(T txn, ContactId c) throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
@@ -845,8 +852,8 @@ interface Database<T> {
* of the given message with respect to the given contact, using the latency
* of the transport over which it was sent.
*/
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m, long maxLatency)
throws DbException;
void updateExpiryTimeAndEta(T txn, ContactId c, MessageId m,
long maxLatency) throws DbException;
/**
* Stores the given transport keys, deleting any keys they have replaced.

View File

@@ -750,6 +750,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getUnackedMessagesToSend(txn, c);
}
@Override
public void resetUnackedMessagesToSend(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
db.resetUnackedMessagesToSend(txn, c);
}
@Override
public long getUnackedMessageBytesToSend(Transaction transaction,
ContactId c) throws DbException {

View File

@@ -429,8 +429,11 @@ abstract class JdbcDatabase implements Database<Connection> {
compactAndClose();
logDuration(LOG, "Compacting database", start);
// Allow the next transaction to reopen the DB
synchronized (connectionsLock) {
connectionsLock.lock();
try {
closed = false;
} finally {
connectionsLock.unlock();
}
txn = startTransaction();
try {
@@ -3290,6 +3293,29 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void resetUnackedMessagesToSend(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE statuses SET expiry = 0, txCount = 0, eta = 0"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
int affected = ps.executeUpdate();
if (affected < 0) {
throw new DbStateException();
}
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void setCleanupTimerDuration(Connection txn, MessageId m,
long duration) throws DbException {

View File

@@ -1,18 +1,49 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import javax.annotation.Nonnull;
import javax.inject.Singleton;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
import okhttp3.Dns;
import okhttp3.OkHttpClient;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@Module
public class IoModule {
private static final int CONNECT_TIMEOUT = 60_000; // Milliseconds
@Provides
@Singleton
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
return timeoutMonitor;
}
// Share an HTTP client instance between requests where possible, while
// allowing the client to be garbage-collected between requests. The
// provider keeps a weak reference to the last client instance and reuses
// the instance until it gets garbage-collected. See
// https://medium.com/@leandromazzuquini/if-you-are-using-okhttp-you-should-know-this-61d68e065a2b
@Provides
@Singleton
WeakSingletonProvider<OkHttpClient> provideOkHttpClientProvider(
SocketFactory torSocketFactory, Dns noDnsLookups) {
return new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return new OkHttpClient.Builder()
.socketFactory(torSocketFactory)
.dns(noDnsLookups) // Don't make local DNS lookups
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
.build();
}
};
}
}

View File

@@ -0,0 +1,177 @@
package org.briarproject.bramble.mailbox;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.briarproject.bramble.api.contact.ContactId;
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 java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
interface MailboxApi {
/**
* Sets up the mailbox with the setup token.
*
* @param properties MailboxProperties with the setup token
* @return the owner token
* @throws ApiException for 401 response.
*/
MailboxAuthToken setup(MailboxProperties properties)
throws IOException, ApiException;
/**
* Checks the status of the mailbox.
*
* @return true if the status is OK, false otherwise.
* @throws ApiException for 401 response.
*/
boolean checkStatus(MailboxProperties properties)
throws IOException, ApiException;
/**
* Unpairs Briar and the mailbox (owner only).
* Resets mailbox state to that after first install
* (e.g. removes all stored files as well).
*/
void wipeMailbox(MailboxProperties properties)
throws IOException, ApiException;
/**
* Adds a new contact to the mailbox.
*
* @throws TolerableFailureException if response code is 409
* (contact was already added).
*/
void addContact(MailboxProperties properties, MailboxContact contact)
throws IOException, ApiException, TolerableFailureException;
/**
* Deletes a contact from the mailbox.
* This should get called after a contact was removed from Briar.
*
* @throws TolerableFailureException if response code is 404
* (contact probably was already deleted).
*/
void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException;
/**
* Gets a list of {@link ContactId}s from the mailbox.
* These are the contacts that the mailbox already knows about.
*/
Collection<ContactId> getContacts(MailboxProperties properties)
throws IOException, ApiException;
/**
* Used by contacts to send files to the owner
* and by the owner to send files to contacts.
* <p>
* The owner can add files to the contacts' inboxes
* and the contacts can add files to their own outbox.
*/
void addFile(MailboxProperties properties, MailboxFolderId folderId,
File file) throws IOException, ApiException;
/**
* Used by owner and contacts to list their files to retrieve.
* <p>
* Returns 200 OK with the list of files in JSON.
*/
List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) throws IOException, ApiException;
/**
* Used by owner and contacts to retrieve a file.
* <p>
* Returns 200 OK if successful with the files' raw bytes
* in the response body.
*
* @param file the empty file the response bytes will be written into.
*/
void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) throws IOException, ApiException;
/**
* Used by owner and contacts to delete files.
* <p>
* Returns 200 OK (no exception) if deletion was successful.
*
* @throws TolerableFailureException on 404 response,
* because file was most likely deleted already.
*/
void deleteFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId)
throws IOException, ApiException, TolerableFailureException;
/**
* Lists all contact outboxes that have files available
* for the owner to download.
*
* @return a list of folder names
* to be used with {@link #getFiles(MailboxProperties, MailboxFolderId)}.
* @throws IllegalArgumentException if used by non-owner.
*/
List<MailboxFolderId> getFolders(MailboxProperties properties)
throws IOException, ApiException;
@Immutable
@JsonSerialize
class MailboxContact {
public final int contactId;
public final MailboxAuthToken token;
public final MailboxFolderId inboxId, outboxId;
MailboxContact(ContactId contactId,
MailboxAuthToken token,
MailboxFolderId inboxId,
MailboxFolderId outboxId) {
this.contactId = contactId.getInt();
this.token = token;
this.inboxId = inboxId;
this.outboxId = outboxId;
}
}
@JsonSerialize
class MailboxFile implements Comparable<MailboxFile> {
public final MailboxFileId name;
public final long time;
public MailboxFile(MailboxFileId name, long time) {
this.name = name;
this.time = time;
}
@Override
public int compareTo(@Nonnull MailboxApi.MailboxFile mailboxFile) {
//noinspection UseCompareMethod
return time < mailboxFile.time ? -1 :
(time == mailboxFile.time ? 0 : 1);
}
}
@Immutable
class ApiException extends Exception {
}
@Immutable
class MailboxAlreadyPairedException extends ApiException {
}
/**
* A failure that does not need to be retried,
* e.g. when adding a contact that already exists.
*/
@Immutable
class TolerableFailureException extends Exception {
}
}

View File

@@ -0,0 +1,301 @@
package org.briarproject.bramble.mailbox;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
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.MailboxId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static com.fasterxml.jackson.databind.MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES;
import static java.util.Objects.requireNonNull;
import static okhttp3.internal.Util.EMPTY_REQUEST;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
@NotNullByDefault
class MailboxApiImpl implements MailboxApi {
private final WeakSingletonProvider<OkHttpClient> httpClientProvider;
private final JsonMapper mapper = JsonMapper.builder()
.enable(BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES)
.build();
private static final MediaType JSON =
requireNonNull(MediaType.parse("application/json; charset=utf-8"));
private static final MediaType FILE =
requireNonNull(MediaType.parse("application/octet-stream"));
@Inject
MailboxApiImpl(WeakSingletonProvider<OkHttpClient> httpClientProvider) {
this.httpClientProvider = httpClientProvider;
}
@Override
public MailboxAuthToken setup(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + "/setup")
.put(EMPTY_REQUEST)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 401) throw new MailboxAlreadyPairedException();
if (!response.isSuccessful()) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
JsonNode tokenNode = node.get("token");
if (tokenNode == null) {
throw new ApiException();
}
String ownerToken = tokenNode.textValue();
return MailboxAuthToken.fromString(ownerToken);
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
@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();
}
@Override
public void wipeMailbox(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + "/")
.delete()
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() != 204) throw new ApiException();
}
/* Contact Management API (owner only) */
@Override
public void addContact(MailboxProperties properties, MailboxContact contact)
throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException();
byte[] bodyBytes = mapper.writeValueAsBytes(contact);
RequestBody body = RequestBody.create(JSON, bodyBytes);
Response response = sendPostRequest(properties, "/contacts", body);
if (response.code() == 409) throw new TolerableFailureException();
if (!response.isSuccessful()) throw new ApiException();
}
@Override
public void deleteContact(MailboxProperties properties, ContactId contactId)
throws IOException, ApiException, TolerableFailureException {
if (!properties.isOwner()) throw new IllegalArgumentException();
String url = properties.getBaseUrl() + "/contacts/" +
contactId.getInt();
Request request = getRequestBuilder(properties.getAuthToken())
.delete()
.url(url)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException();
}
@Override
public Collection<ContactId> getContacts(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/contacts");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
ArrayNode contactsNode = getArray(node, "contacts");
List<ContactId> list = new ArrayList<>();
for (JsonNode contactNode : contactsNode) {
if (!contactNode.isNumber()) throw new ApiException();
int id = contactNode.intValue();
if (id < 1) throw new ApiException();
list.add(new ContactId(id));
}
return list;
} catch (JacksonException e) {
throw new ApiException();
}
}
/* File Management (owner and contacts) */
@Override
public void addFile(MailboxProperties properties, MailboxFolderId folderId,
File file) throws IOException, ApiException {
String path = "/files/" + folderId;
RequestBody body = RequestBody.create(FILE, file);
Response response = sendPostRequest(properties, path, body);
if (response.code() != 200) throw new ApiException();
}
@Override
public List<MailboxFile> getFiles(MailboxProperties properties,
MailboxFolderId folderId) throws IOException, ApiException {
String path = "/files/" + folderId;
Response response = sendGetRequest(properties, path);
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
ArrayNode filesNode = getArray(node, "files");
List<MailboxFile> list = new ArrayList<>();
for (JsonNode fileNode : filesNode) {
if (!fileNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) fileNode;
JsonNode nameNode = objectNode.get("name");
JsonNode timeNode = objectNode.get("time");
if (nameNode == null || !nameNode.isTextual()) {
throw new ApiException();
}
if (timeNode == null || !timeNode.isNumber()) {
throw new ApiException();
}
String name = nameNode.asText();
long time = timeNode.asLong();
if (time < 1) throw new ApiException();
list.add(new MailboxFile(MailboxFileId.fromString(name), time));
}
Collections.sort(list);
return list;
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
@Override
public void getFile(MailboxProperties properties, MailboxFolderId folderId,
MailboxFileId fileId, File file) throws IOException, ApiException {
String path = "/files/" + folderId + "/" + fileId;
Response response = sendGetRequest(properties, path);
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
FileOutputStream outputStream = new FileOutputStream(file);
copyAndClose(body.byteStream(), outputStream);
}
@Override
public void deleteFile(MailboxProperties properties,
MailboxFolderId folderId, MailboxFileId fileId)
throws IOException, ApiException, TolerableFailureException {
String path = "/files/" + folderId + "/" + fileId;
Request request = getRequestBuilder(properties.getAuthToken())
.delete()
.url(properties.getBaseUrl() + path)
.build();
OkHttpClient client = httpClientProvider.get();
Response response = client.newCall(request).execute();
if (response.code() == 404) throw new TolerableFailureException();
if (response.code() != 200) throw new ApiException();
}
@Override
public List<MailboxFolderId> getFolders(MailboxProperties properties)
throws IOException, ApiException {
if (!properties.isOwner()) throw new IllegalArgumentException();
Response response = sendGetRequest(properties, "/folders");
if (response.code() != 200) throw new ApiException();
ResponseBody body = response.body();
if (body == null) throw new ApiException();
try {
JsonNode node = mapper.readTree(body.string());
ArrayNode filesNode = getArray(node, "folders");
List<MailboxFolderId> list = new ArrayList<>();
for (JsonNode fileNode : filesNode) {
if (!fileNode.isObject()) throw new ApiException();
ObjectNode objectNode = (ObjectNode) fileNode;
JsonNode idNode = objectNode.get("id");
if (idNode == null || !idNode.isTextual()) {
throw new ApiException();
}
String id = idNode.asText();
list.add(MailboxFolderId.fromString(id));
}
return list;
} catch (JacksonException | InvalidMailboxIdException e) {
throw new ApiException();
}
}
/* Helper Functions */
private Response sendGetRequest(MailboxProperties properties, String path)
throws IOException {
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + path)
.build();
OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute();
}
private Response sendPostRequest(MailboxProperties properties, String path,
RequestBody body) throws IOException {
Request request = getRequestBuilder(properties.getAuthToken())
.url(properties.getBaseUrl() + path)
.post(body)
.build();
OkHttpClient client = httpClientProvider.get();
return client.newCall(request).execute();
}
private Request.Builder getRequestBuilder(MailboxId token) {
return new Request.Builder()
.addHeader("Authorization", "Bearer " + token);
}
/* JSON helpers */
private ArrayNode getArray(JsonNode node, String name) throws ApiException {
JsonNode arrayNode = node.get(name);
if (arrayNode == null || !arrayNode.isArray()) {
throw new ApiException();
}
return (ArrayNode) arrayNode;
}
}

View File

@@ -0,0 +1,78 @@
package org.briarproject.bramble.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.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class MailboxManagerImpl implements MailboxManager {
private final Executor ioExecutor;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxPairingTaskFactory pairingTaskFactory;
private final Object lock = new Object();
@Nullable
@GuardedBy("lock")
private MailboxPairingTask pairingTask = null;
@Inject
MailboxManagerImpl(
@IoExecutor Executor ioExecutor,
MailboxSettingsManager mailboxSettingsManager,
MailboxPairingTaskFactory pairingTaskFactory) {
this.ioExecutor = ioExecutor;
this.mailboxSettingsManager = mailboxSettingsManager;
this.pairingTaskFactory = pairingTaskFactory;
}
@Override
public boolean isPaired(Transaction txn) throws DbException {
return mailboxSettingsManager.getOwnMailboxProperties(txn) != null;
}
@Override
public MailboxStatus getMailboxStatus(Transaction txn) throws DbException {
return mailboxSettingsManager.getOwnMailboxStatus(txn);
}
@Nullable
@Override
public MailboxPairingTask getCurrentPairingTask() {
synchronized (lock) {
return pairingTask;
}
}
@Override
public MailboxPairingTask startPairingTask(String payload) {
MailboxPairingTask created;
synchronized (lock) {
if (pairingTask != null) return pairingTask;
created = pairingTaskFactory.createPairingTask(payload);
pairingTask = created;
}
ioExecutor.execute(() -> {
created.run();
synchronized (lock) {
// remove task after it finished
pairingTask = null;
}
});
return created;
}
}

View File

@@ -0,0 +1,36 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class MailboxModule {
@Provides
@Singleton
MailboxManager providesMailboxManager(MailboxManagerImpl mailboxManager) {
return mailboxManager;
}
@Provides
MailboxPairingTaskFactory provideMailboxPairingTaskFactory(
MailboxPairingTaskFactoryImpl mailboxPairingTaskFactory) {
return mailboxPairingTaskFactory;
}
@Provides
MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager;
}
@Provides
MailboxApi providesMailboxApi(MailboxApiImpl mailboxApi) {
return mailboxApi;
}
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface MailboxPairingTaskFactory {
MailboxPairingTask createPairingTask(String qrCodePayload);
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.TransactionManager;
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final Executor eventExecutor;
private final TransactionManager db;
private final CryptoComponent crypto;
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
@Inject
MailboxPairingTaskFactoryImpl(
@EventExecutor Executor eventExecutor,
TransactionManager db,
CryptoComponent crypto,
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager) {
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
}
@Override
public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager);
}
}

View File

@@ -0,0 +1,172 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.CryptoComponent;
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
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 MailboxPairingTaskImpl implements MailboxPairingTask {
private final static Logger LOG =
getLogger(MailboxPairingTaskImpl.class.getName());
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int VERSION_REQUIRED = 32;
private final String payload;
private final Executor eventExecutor;
private final TransactionManager db;
private final CryptoComponent crypto;
private final Clock clock;
private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<Consumer<MailboxPairingState>> observers =
new ArrayList<>();
@GuardedBy("lock")
private MailboxPairingState state;
MailboxPairingTaskImpl(
String payload,
@EventExecutor Executor eventExecutor,
TransactionManager db,
CryptoComponent crypto,
Clock clock,
MailboxApi api,
MailboxSettingsManager mailboxSettingsManager) {
this.payload = payload;
this.eventExecutor = eventExecutor;
this.db = db;
this.crypto = crypto;
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
state = new MailboxPairingState.QrCodeReceived(payload);
}
@Override
public void addObserver(Consumer<MailboxPairingState> o) {
MailboxPairingState state;
synchronized (lock) {
observers.add(o);
state = this.state;
eventExecutor.execute(() -> o.accept(state));
}
}
@Override
public void removeObserver(Consumer<MailboxPairingState> o) {
synchronized (lock) {
observers.remove(o);
}
}
@Override
public void run() {
try {
pairMailbox();
} catch (FormatException e) {
onMailboxError(e, new MailboxPairingState.InvalidQrCode());
} catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
} catch (IOException e) {
onMailboxError(e, new MailboxPairingState.ConnectionError(payload));
} catch (ApiException | DbException e) {
onMailboxError(e, new MailboxPairingState.UnexpectedError(payload));
}
}
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);
long time = clock.currentTimeMillis();
db.transaction(false, txn -> {
mailboxSettingsManager
.setOwnMailboxProperties(txn, ownerProperties);
mailboxSettingsManager.recordSuccessfulConnection(txn, time);
});
setState(new MailboxPairingState.Paired());
}
private void onMailboxError(Exception e, MailboxPairingState state) {
logException(LOG, WARNING, e);
setState(state);
}
private void setState(MailboxPairingState state) {
synchronized (lock) {
this.state = state;
notifyObservers();
}
}
@GuardedBy("lock")
private void notifyObservers() {
List<Consumer<MailboxPairingState>> observers =
new ArrayList<>(this.observers);
MailboxPairingState state = this.state;
eventExecutor.execute(() -> {
for (Consumer<MailboxPairingState> o : observers) o.accept(state);
});
}
private MailboxProperties decodeQrCodePayload(String payload)
throws FormatException {
byte[] bytes = payload.getBytes(ISO_8859_1);
if (bytes.length != 65) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code length is not 65: " + bytes.length);
}
throw new FormatException();
}
int version = bytes[0] & 0xFF;
if (version != VERSION_REQUIRED) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code has not version " + VERSION_REQUIRED +
": " + version);
}
throw new FormatException();
}
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onionAddress = crypto.encodeOnionAddress(onionPubKey);
String baseUrl = "http://" + onionAddress + ".onion";
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(baseUrl, setupToken, true);
}
}

View File

@@ -0,0 +1,116 @@
package org.briarproject.bramble.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.mailbox.InvalidMailboxIdException;
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
// Package access for testing
static final String SETTINGS_NAMESPACE = "mailbox";
static final String SETTINGS_KEY_ONION = "onion";
static final String SETTINGS_KEY_TOKEN = "token";
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;
@Inject
MailboxSettingsManagerImpl(SettingsManager settingsManager) {
this.settingsManager = settingsManager;
}
@Override
public MailboxProperties getOwnMailboxProperties(Transaction txn)
throws DbException {
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String onion = s.get(SETTINGS_KEY_ONION);
String token = s.get(SETTINGS_KEY_TOKEN);
if (isNullOrEmpty(onion) || isNullOrEmpty(token)) return null;
try {
MailboxAuthToken tokenId = MailboxAuthToken.fromString(token);
return new MailboxProperties(onion, tokenId, true);
} catch (InvalidMailboxIdException e) {
throw new DbException(e);
}
}
@Override
public void setOwnMailboxProperties(Transaction txn, MailboxProperties p)
throws DbException {
Settings s = new Settings();
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
}
@Override
public MailboxStatus getOwnMailboxStatus(Transaction txn)
throws DbException {
Settings s = settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
long lastAttempt = s.getLong(SETTINGS_KEY_LAST_ATTEMPT, -1);
long lastSuccess = s.getLong(SETTINGS_KEY_LAST_SUCCESS, -1);
int attempts = s.getInt(SETTINGS_KEY_ATTEMPTS, 0);
return new MailboxStatus(lastAttempt, lastSuccess, attempts);
}
@Override
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
Settings s = new Settings();
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
}
@Override
public void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
int attempts = oldSettings.getInt(SETTINGS_KEY_ATTEMPTS, 0);
Settings newSettings = new Settings();
newSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
newSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts + 1);
settingsManager.mergeSettings(txn, newSettings, SETTINGS_NAMESPACE);
}
@Override
public void setPendingUpload(Transaction txn, ContactId id,
@Nullable String filename) throws DbException {
Settings s = new Settings();
String value = filename == null ? "" : filename;
s.put(String.valueOf(id.getInt()), value);
settingsManager.mergeSettings(txn, s, SETTINGS_UPLOADS_NAMESPACE);
}
@Nullable
@Override
public String getPendingUpload(Transaction txn, ContactId id)
throws DbException {
Settings s =
settingsManager.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
String filename = s.get(String.valueOf(id.getInt()));
if (isNullOrEmpty(filename)) return null;
return filename;
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
@Deprecated // We can simply remove tasks when they finish
@NotNullByDefault
interface RemovableDriveTaskRegistry {

View File

@@ -1,40 +1,71 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
// TODO: Create a module for this so it doesn't have to be public
@NotNullByDefault
public interface CircumventionProvider {
enum BridgeType {
DEFAULT_OBFS4,
NON_DEFAULT_OBFS4,
MEEK
}
/**
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
*
* <p>
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
/**
* Countries where obfs4 or meek bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED}.
* Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_OBFS4_BRIDGES}, {@link #NON_DEFAULT_OBFS4_BRIDGES} and
* {@link #MEEK_BRIDGES}.
*/
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };
String[] BRIDGES = {"CN", "IR", "EG", "BY", "TR", "SY", "VE", "RU"};
/**
* Countries where default obfs4 bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] DEFAULT_OBFS4_BRIDGES = {"EG", "BY", "TR", "SY", "VE"};
/**
* Countries where non-default obfs4 bridges are likely to work.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NON_DEFAULT_OBFS4_BRIDGES = {"RU"};
/**
* Countries where obfs4 bridges won't work and meek is needed.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NEEDS_MEEK = {"CN", "IR"};
String[] MEEK_BRIDGES = {"CN", "IR"};
/**
* Returns true if vanilla Tor connections are blocked in the given country.
*/
boolean isTorProbablyBlocked(String countryCode);
/**
* Returns true if bridge connections of some type work in the given
* country.
*/
boolean doBridgesWork(String countryCode);
boolean needsMeek(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.
*/
BridgeType getBestBridgeType(String countryCode);
@IoExecutor
List<String> getBridges(boolean meek);
List<String> getBridges(BridgeType type);
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream;
import java.util.ArrayList;
@@ -9,24 +10,31 @@ import java.util.List;
import java.util.Scanner;
import java.util.Set;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Arrays.asList;
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;
@Immutable
@NotNullByDefault
class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges";
private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(asList(BLOCKED));
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
private static final Set<String> BRIDGE_COUNTRIES =
new HashSet<>(asList(BRIDGES));
private static final Set<String> BRIDGES_NEED_MEEK =
new HashSet<>(asList(NEEDS_MEEK));
@Nullable
private volatile List<String> bridges = null;
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));
private static final Set<String> MEEK_COUNTRIES =
new HashSet<>(asList(MEEK_BRIDGES));
@Inject
CircumventionProviderImpl() {
@@ -39,33 +47,42 @@ class CircumventionProviderImpl implements CircumventionProvider {
@Override
public boolean doBridgesWork(String countryCode) {
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
return BRIDGE_COUNTRIES.contains(countryCode);
}
@Override
public boolean needsMeek(String countryCode) {
return BRIDGES_NEED_MEEK.contains(countryCode);
public BridgeType getBestBridgeType(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;
} else if (MEEK_COUNTRIES.contains(countryCode)) {
return MEEK;
} else {
return DEFAULT_OBFS4;
}
}
@Override
@IoExecutor
public List<String> getBridges(boolean useMeek) {
List<String> bridges = this.bridges;
if (bridges != null) return new ArrayList<>(bridges);
InputStream is = getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME);
public List<String> getBridges(BridgeType type) {
InputStream is = requireNonNull(getClass().getClassLoader()
.getResourceAsStream(BRIDGE_FILE_NAME));
Scanner scanner = new Scanner(is);
bridges = new ArrayList<>();
List<String> bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
boolean isMeekBridge = line.startsWith("Bridge meek");
if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue;
if (!line.startsWith("#")) bridges.add(line);
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)) {
bridges.add(line.substring(2));
}
}
scanner.close();
this.bridges = bridges;
return bridges;
}

View File

@@ -33,7 +33,9 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
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.plugin.tor.CircumventionProvider.BridgeType;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
@@ -44,6 +46,7 @@ import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -68,20 +71,16 @@ import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_PRIVATE_KEY_V3;
import static org.briarproject.bramble.api.plugin.TorConstants.HS_V3_CREATED;
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;
@@ -90,12 +89,11 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_
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;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
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.api.plugin.TorConstants.V3_MIGRATION_PERIOD_MS;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
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;
@@ -115,7 +113,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V2 = Pattern.compile("[a-z2-7]{16}");
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
private final Executor ioExecutor, wakefulIoExecutor;
@@ -132,8 +129,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider;
private final long maxLatency;
private final int maxIdleTime, socketTimeout;
private final int maxIdleTime;
private final int socketTimeout;
private final File torDirectory, geoIpFile, configFile;
private final int torSocksPort;
private final int torControlPort;
private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false);
@@ -162,7 +162,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory) {
File torDirectory,
int torSocksPort,
int torControlPort) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager;
@@ -182,6 +184,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
geoIpFile = new File(torDirectory, "geoip");
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
@@ -277,6 +281,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
throw new PluginException();
}
//noinspection BusyWait
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
}
LOG.info("Auth cookie created");
@@ -287,7 +292,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
try {
// Open a control connection and authenticate using the cookie file
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
controlSocket = new Socket("127.0.0.1", torControlPort);
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
@@ -390,9 +395,24 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return zin;
}
private static void append(StringBuilder strb, String name, int value) {
strb.append(name);
strb.append(" ");
strb.append(value);
strb.append("\n");
}
private InputStream getConfigInputStream() {
ClassLoader cl = getClass().getClassLoader();
return requireNonNull(cl.getResourceAsStream("torrc"));
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
//noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
}
private void listFiles(File f) {
@@ -457,54 +477,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void publishHiddenService(String port) {
if (!state.isTorRunning()) return;
// TODO: Remove support for v2 hidden services after a reasonable
// migration period (migration started 2020-06-30)
String privKey2 = settings.get(HS_PRIVATE_KEY_V2);
String privKey3 = settings.get(HS_PRIVATE_KEY_V3);
String v3Created = settings.get(HS_V3_CREATED);
// Publish a v2 hidden service if we've already created one, and
// either we've never published a v3 hidden service or we're still
// in the migration period since first publishing it
if (!isNullOrEmpty(privKey2)) {
long now = clock.currentTimeMillis();
long then = v3Created == null ? now : Long.parseLong(v3Created);
if (now - then >= V3_MIGRATION_PERIOD_MS) retireV2HiddenService();
else publishV2HiddenService(port, privKey2);
}
publishV3HiddenService(port, privKey3);
}
private void publishV2HiddenService(String port, String privKey) {
LOG.info("Creating v2 hidden service");
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
Map<String, String> response;
try {
response = controlConnection.addOnion(privKey, portLines);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
}
if (!response.containsKey(HS_ADDRESS)) {
LOG.warning("Tor did not return a hidden service address");
return;
}
String onion2 = response.get(HS_ADDRESS);
if (LOG.isLoggable(INFO)) {
LOG.info("V2 hidden service " + scrubOnion(onion2));
}
// The hostname has already been published and the private key stored
}
private void retireV2HiddenService() {
LOG.info("Retiring v2 hidden service");
TransportProperties p = new TransportProperties();
p.put(PROP_ONION_V2, "");
callback.mergeLocalProperties(p);
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V2, "");
callback.mergeSettings(s);
}
private void publishV3HiddenService(String port, @Nullable String privKey) {
LOG.info("Creating v3 hidden service");
Map<Integer, String> portLines = singletonMap(80, "127.0.0.1:" + port);
@@ -541,7 +517,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Save the hidden service's private key for next time
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
s.put(HS_V3_CREATED, String.valueOf(clock.currentTimeMillis()));
callback.mergeSettings(s);
}
}
@@ -569,20 +544,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("DisableNetwork", enable ? "0" : "1");
}
private void enableBridges(boolean enable, boolean needsMeek)
private void enableBridges(boolean enable, BridgeType bridgeType)
throws IOException {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (needsMeek) {
if (bridgeType == MEEK) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
} else {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
conf.addAll(circumventionProvider.getBridges(needsMeek));
conf.addAll(circumventionProvider.getBridges(bridgeType));
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
@@ -648,49 +623,30 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (getState() != ACTIVE) return null;
// TODO: Remove support for v2 hidden services after a reasonable
// migration period (migration started 2020-06-30)
String bestOnion = null, version = null;
String onion2 = p.get(PROP_ONION_V2);
String onion3 = p.get(PROP_ONION_V3);
if (!isNullOrEmpty(onion2)) {
if (ONION_V2.matcher(onion2).matches()) {
bestOnion = onion2;
version = "v2";
} else {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO))
LOG.info("Invalid v2 hostname: " + onion2);
if (onion3 != null && !ONION_V3.matcher(onion3).matches()) {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO)) {
LOG.info("Invalid v3 hostname: " + onion3);
}
onion3 = null;
}
if (!isNullOrEmpty(onion3)) {
if (ONION_V3.matcher(onion3).matches()) {
bestOnion = onion3;
version = "v3";
} else {
// Don't scrub the address so we can find the problem
if (LOG.isLoggable(INFO))
LOG.info("Invalid v3 hostname: " + onion3);
}
}
if (bestOnion == null) return null;
if (onion3 == null) return null;
Socket s = null;
try {
if (LOG.isLoggable(INFO)) {
LOG.info("Connecting to " + version + " "
+ scrubOnion(bestOnion));
LOG.info("Connecting to v3 " + scrubOnion(onion3));
}
s = torSocketFactory.createSocket(bestOnion + ".onion", 80);
s = torSocketFactory.createSocket(onion3 + ".onion", 80);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) {
LOG.info("Connected to " + version + " "
+ scrubOnion(bestOnion));
LOG.info("Connected to v3 " + scrubOnion(onion3));
}
return new TorTransportConnection(this, s);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to " + version + " "
+ scrubOnion(bestOnion) + ": " + e.toString());
LOG.info("Could not connect to v3 "
+ scrubOnion(onion3) + ": " + e.toString());
}
tryToClose(s, LOG, WARNING);
return null;
@@ -890,7 +846,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
int reasonsDisabled = 0;
boolean enableNetwork = false, enableBridges = false;
boolean useMeek = false, enableConnectionPadding = false;
boolean enableConnectionPadding = false;
BridgeType bridgeType =
circumventionProvider.getBestBridgeType(country);
if (!online) {
LOG.info("Disabling network, device is offline");
@@ -919,14 +877,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (ipv6Only ||
circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges");
enableBridges = true;
useMeek = true;
} else {
LOG.info("Using obfs4 bridges");
enableBridges = true;
if (ipv6Only) bridgeType = MEEK;
enableBridges = true;
if (LOG.isLoggable(INFO)) {
LOG.info("Using bridge type " + bridgeType);
}
} else {
LOG.info("Not using bridges");
@@ -944,7 +898,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try {
if (enableNetwork) {
enableBridges(enableBridges, useMeek);
enableBridges(enableBridges, bridgeType);
enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
}
@@ -984,17 +938,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Nullable
private ServerSocket serverSocket = null;
synchronized void setStarted() {
private synchronized void setStarted() {
started = true;
callback.pluginStateChanged(getState());
}
synchronized boolean isTorRunning() {
private synchronized boolean isTorRunning() {
return started && !stopped;
}
@Nullable
synchronized ServerSocket setStopped() {
private synchronized ServerSocket setStopped() {
stopped = true;
ServerSocket ss = serverSocket;
serverSocket = null;
@@ -1002,44 +956,44 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return ss;
}
synchronized void setBootstrapped() {
private synchronized void setBootstrapped() {
bootstrapped = true;
callback.pluginStateChanged(getState());
}
synchronized boolean getAndSetCircuitBuilt() {
private synchronized boolean getAndSetCircuitBuilt() {
boolean firstCircuit = !circuitBuilt;
circuitBuilt = true;
callback.pluginStateChanged(getState());
return firstCircuit;
}
synchronized void enableNetwork(boolean enable) {
private synchronized void enableNetwork(boolean enable) {
networkInitialised = true;
networkEnabled = enable;
if (!enable) circuitBuilt = false;
callback.pluginStateChanged(getState());
}
synchronized void setReasonsDisabled(int reasonsDisabled) {
private synchronized void setReasonsDisabled(int reasonsDisabled) {
settingsChecked = true;
this.reasonsDisabled = reasonsDisabled;
callback.pluginStateChanged(getState());
}
// Doesn't affect getState()
synchronized boolean setServerSocket(ServerSocket ss) {
private synchronized boolean setServerSocket(ServerSocket ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
return true;
}
// Doesn't affect getState()
synchronized void clearServerSocket(ServerSocket ss) {
private synchronized void clearServerSocket(ServerSocket ss) {
if (serverSocket == ss) serverSocket = null;
}
synchronized State getState() {
private synchronized State getState() {
if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING;
}
@@ -1049,7 +1003,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
}
synchronized int getReasonsDisabled() {
private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
}
}

View File

@@ -4,39 +4,26 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.util.encoders.Base64;
import org.briarproject.bramble.util.Base32;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import java.nio.charset.Charset;
import static java.lang.System.arraycopy;
public class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
private static final EdDSANamedCurveSpec CURVE_SPEC =
EdDSANamedCurveTable.getByName("Ed25519");
private static final byte HS_PROTOCOL_VERSION = 3;
private static final int CHECKSUM_BYTES = 2;
private final CryptoComponent crypto;
TorRendezvousCryptoImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
@Override
public String getOnionAddress(byte[] seed) {
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
byte[] publicKey = spec.getA().toByteArray();
Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII"));
digest.update(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length);
digest.update(HS_PROTOCOL_VERSION);
byte[] checksum = new byte[digest.getDigestSize()];
digest.doFinal(checksum, 0);
byte[] address = new byte[publicKey.length + CHECKSUM_BYTES + 1];
arraycopy(publicKey, 0, address, 0, publicKey.length);
arraycopy(checksum, 0, address, publicKey.length, CHECKSUM_BYTES);
address[address.length - 1] = HS_PROTOCOL_VERSION;
return Base32.encode(address).toLowerCase();
return crypto.encodeOnionAddress(spec.getA().toByteArray());
}
@Override

View File

@@ -37,4 +37,10 @@ class SettingsManagerImpl implements SettingsManager {
public void mergeSettings(Settings s, String namespace) throws DbException {
db.transaction(false, txn -> db.mergeSettings(txn, s, namespace));
}
@Override
public void mergeSettings(Transaction txn, Settings s, String namespace)
throws DbException {
db.mergeSettings(txn, s, namespace);
}
}

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.socks;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import java.net.InetSocketAddress;
import javax.net.SocketFactory;
@@ -9,15 +11,14 @@ import dagger.Provides;
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
import static org.briarproject.bramble.api.plugin.TorConstants.SOCKS_PORT;
@Module
public class SocksModule {
@Provides
SocketFactory provideTorSocketFactory() {
SocketFactory provideTorSocketFactory(@TorSocksPort int torSocksPort) {
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
SOCKS_PORT);
torSocksPort);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
EXTRA_SOCKET_TIMEOUT);
}

View File

@@ -215,6 +215,23 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
m.getStreamContext(txn, tag)));
}
@Override
public StreamContext getStreamContextOnly(TransportId t, byte[] tag)
throws DbException {
return withManager(t, m ->
db.transactionWithNullableResult(false, txn ->
m.getStreamContextOnly(txn, tag)));
}
@Override
public void markTagAsRecognised(TransportId t, byte[] tag)
throws DbException {
withManager(t, m -> {
db.transaction(false, txn -> m.markTagAsRecognised(txn, tag));
return null;
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {

View File

@@ -48,4 +48,9 @@ interface TransportKeyManager {
StreamContext getStreamContext(Transaction txn, byte[] tag)
throws DbException;
@Nullable
StreamContext getStreamContextOnly(Transaction txn, byte[] tag);
void markTagAsRecognised(Transaction txn, byte[] tag) throws DbException;
}

View File

@@ -393,56 +393,82 @@ class TransportKeyManagerImpl implements TransportKeyManager {
throws DbException {
lock.lock();
try {
// Look up the incoming keys for the tag
TagContext tagCtx = inContexts.remove(new Bytes(tag));
if (tagCtx == null) return null;
MutableIncomingKeys inKeys = tagCtx.inKeys;
// Create a stream context
StreamContext ctx = new StreamContext(tagCtx.contactId,
tagCtx.pendingContactId, transportId,
inKeys.getTagKey(), inKeys.getHeaderKey(),
tagCtx.streamNumber, tagCtx.handshakeMode);
// Update the reordering window
ReorderingWindow window = inKeys.getWindow();
Change change = window.setSeen(tagCtx.streamNumber);
// Add tags for any stream numbers added to the window
for (long streamNumber : change.getAdded()) {
byte[] addTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
tagCtx.contactId, tagCtx.pendingContactId, inKeys,
streamNumber, tagCtx.handshakeMode);
inContexts.put(new Bytes(addTag), tagCtx1);
}
// Remove tags for any stream numbers removed from the window
for (long streamNumber : change.getRemoved()) {
if (streamNumber == tagCtx.streamNumber) continue;
byte[] removeTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.remove(new Bytes(removeTag));
}
// Write the window back to the DB
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
inKeys.getTimePeriod(), window.getBase(),
window.getBitmap());
// If the outgoing keys are inactive, activate them
MutableTransportKeySet ks = keys.get(tagCtx.keySetId);
MutableOutgoingKeys outKeys =
ks.getKeys().getCurrentOutgoingKeys();
if (!outKeys.isActive()) {
LOG.info("Activating outgoing keys");
outKeys.activate();
considerReplacingOutgoingKeys(ks);
db.setTransportKeysActive(txn, transportId, tagCtx.keySetId);
}
StreamContext ctx = streamContextFromTag(tag);
if (ctx == null) return null;
markTagAsRecognised(txn, tag);
return ctx;
} finally {
lock.unlock();
}
}
@Override
public StreamContext getStreamContextOnly(Transaction txn, byte[] tag) {
lock.lock();
try {
return streamContextFromTag(tag);
} finally {
lock.unlock();
}
}
@GuardedBy("lock")
@Nullable
private StreamContext streamContextFromTag(byte[] tag) {
// Look up the incoming keys for the tag
TagContext tagCtx = inContexts.get(new Bytes(tag));
if (tagCtx == null) return null;
MutableIncomingKeys inKeys = tagCtx.inKeys;
// Create a stream context
return new StreamContext(tagCtx.contactId,
tagCtx.pendingContactId, transportId,
inKeys.getTagKey(), inKeys.getHeaderKey(),
tagCtx.streamNumber, tagCtx.handshakeMode);
}
@Override
public void markTagAsRecognised(Transaction txn, byte[] tag)
throws DbException {
TagContext tagCtx = inContexts.remove(new Bytes(tag));
if (tagCtx == null) return;
MutableIncomingKeys inKeys = tagCtx.inKeys;
// Update the reordering window
ReorderingWindow window = inKeys.getWindow();
Change change = window.setSeen(tagCtx.streamNumber);
// Add tags for any stream numbers added to the window
for (long streamNumber : change.getAdded()) {
byte[] addTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
tagCtx.contactId, tagCtx.pendingContactId, inKeys,
streamNumber, tagCtx.handshakeMode);
inContexts.put(new Bytes(addTag), tagCtx1);
}
// Remove tags for any stream numbers removed from the window
for (long streamNumber : change.getRemoved()) {
if (streamNumber == tagCtx.streamNumber) continue;
byte[] removeTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.remove(new Bytes(removeTag));
}
// Write the window back to the DB
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
inKeys.getTimePeriod(), window.getBase(),
window.getBitmap());
// If the outgoing keys are inactive, activate them
MutableTransportKeySet ks = keys.get(tagCtx.keySetId);
MutableOutgoingKeys outKeys =
ks.getKeys().getCurrentOutgoingKeys();
if (!outKeys.isActive()) {
LOG.info("Activating outgoing keys");
outKeys.activate();
considerReplacingOutgoingKeys(ks);
db.setTransportKeysActive(txn, transportId, tagCtx.keySetId);
}
}
@DatabaseExecutor
@Wakeful
private void updateKeys(Transaction txn) throws DbException {

View File

@@ -1,10 +1,20 @@
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
d Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
d Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
d Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
d Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
d Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
d Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
d Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
d Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
d Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
d Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
d Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
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
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -1,6 +0,0 @@
ControlPort 59051
CookieAuthentication 1
DisableNetwork 1
RunAsDaemon 1
SafeSocks 1
SocksPort 59050

View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -e
URL="http://127.0.0.1:8000/status"
attempt_counter=0
max_attempts=200 # 10min - CI for mailbox currently takes ~5min
echo "Waiting for mailbox to come online at $URL"
until [[ "$(curl -s -o /dev/null -w '%{http_code}' $URL)" == "401" ]]; do
if [ ${attempt_counter} -eq ${max_attempts} ]; then
echo "Timed out waiting for mailbox"
exit 1
fi
printf '.'
attempt_counter=$((attempt_counter + 1))
sleep 3
done
echo "Mailbox started"

View File

@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.test.ValidatorTestCase;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
@@ -38,7 +38,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
private final Metadata meta = new Metadata();
public BdfMessageValidatorTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
}
@Test(expected = InvalidMessageException.class)

View File

@@ -24,11 +24,10 @@ 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.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.util.StringUtils;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -53,9 +52,8 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class ClientHelperImplTest extends BrambleTestCase {
public class ClientHelperImplTest extends BrambleMockTestCase {
private final Mockery context = new Mockery();
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
@@ -100,7 +98,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.addLocalMessage(message, dictionary, shared);
context.assertIsSatisfied();
}
@Test
@@ -112,7 +109,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.createMessage(groupId, timestamp, list);
context.assertIsSatisfied();
}
@Test
@@ -127,7 +123,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.getMessageAsList(messageId);
context.assertIsSatisfied();
}
@Test
@@ -144,7 +139,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertEquals(dictionary,
clientHelper.getGroupMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@Test
@@ -161,7 +155,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertEquals(dictionary,
clientHelper.getMessageMetadataAsDictionary(messageId));
context.assertIsSatisfied();
}
@Test
@@ -179,7 +172,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@Test
@@ -204,7 +196,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertEquals(map,
clientHelper.getMessageMetadataAsDictionary(groupId, query));
context.assertIsSatisfied();
}
@Test
@@ -219,7 +210,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.mergeGroupMetadata(groupId, dictionary);
context.assertIsSatisfied();
}
@Test
@@ -234,7 +224,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.mergeMessageMetadata(messageId, dictionary);
context.assertIsSatisfied();
}
@Test
@@ -242,7 +231,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
byte[] bytes = expectToByteArray(list);
assertArrayEquals(bytes, clientHelper.toByteArray(list));
context.assertIsSatisfied();
}
@Test
@@ -250,7 +238,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
expectToList(true);
assertEquals(list, clientHelper.toList(getRandomBytes(123)));
context.assertIsSatisfied();
}
@Test
@@ -262,7 +249,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
fail();
} catch (FormatException e) {
// expected
context.assertIsSatisfied();
}
}
@@ -279,7 +265,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
assertArrayEquals(signature,
clientHelper.sign(label, list, privateKey));
context.assertIsSatisfied();
}
@Test
@@ -295,7 +280,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
clientHelper.verifySignature(signature, label, list, publicKey);
context.assertIsSatisfied();
}
@Test
@@ -315,7 +299,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
fail();
} catch (GeneralSecurityException e) {
// expected
context.assertIsSatisfied();
}
}

View File

@@ -2197,6 +2197,55 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testResetRetransmissionTimes() throws Exception {
long now = System.currentTimeMillis();
AtomicLong time = new AtomicLong(now);
Database<Connection> db =
open(false, new TestMessageFactory(), new SettableClock(time));
Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message
db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Time: now
// Retrieve the message from the database
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
// Time: now
// Mark the message as sent
db.updateExpiryTimeAndEta(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
// Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable
time.set(now + MAX_LATENCY * 2 - 1);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty());
// Reset the retransmission times
db.resetUnackedMessagesToSend(txn, contactId);
// The message should have infinitely short expiry
assertEquals(0, db.getNextSendTime(txn, contactId));
// The message should be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertFalse(ids.isEmpty());
db.commitTransaction(txn);
db.close();
}
@Test
public void testCompactionTime() throws Exception {
MessageFactory messageFactory = new TestMessageFactory();

View File

@@ -11,8 +11,9 @@ import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.test.BrambleTestCase;
import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Rule;
import org.junit.Test;
@@ -34,7 +35,8 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Rule
public JUnitRuleMockery context = new JUnitRuleMockery() {{
// So we can mock concrete classes like KeyAgreementTransport
setImposteriser(ClassImposteriser.INSTANCE);
setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
setThreadingPolicy(new Synchroniser());
}};
private final PublicKey alicePubKey = getAgreementPublicKey();

View File

@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test;
import java.io.InputStream;
@@ -58,7 +58,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
private KeyAgreementTransport kat;
public KeyAgreementTransportTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
inputStream = context.mock(InputStream.class);
outputStream = context.mock(OutputStream.class);
}

View File

@@ -0,0 +1,802 @@
package org.briarproject.bramble.mailbox;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.contact.ContactId;
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.MailboxId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;
import javax.net.SocketFactory;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okio.Buffer;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.readBytes;
import static org.briarproject.bramble.test.TestUtils.writeBytes;
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.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class MailboxApiTest extends BrambleTestCase {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private final OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(SocketFactory.getDefault())
.connectTimeout(60_000, MILLISECONDS)
.build();
private final WeakSingletonProvider<OkHttpClient> httpClientProvider =
new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return client;
}
};
private final MailboxApiImpl api = new MailboxApiImpl(httpClientProvider);
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
private final MailboxAuthToken token2 = new MailboxAuthToken(getRandomId());
private final ContactId contactId = getContactId();
private final MailboxAuthToken contactToken =
new MailboxAuthToken(getRandomId());
private final MailboxFolderId contactInboxId =
new MailboxFolderId(getRandomId());
private final MailboxFolderId contactOutboxId =
new MailboxFolderId(getRandomId());
private final MailboxContact mailboxContact = new MailboxContact(
contactId, contactToken, contactInboxId, contactOutboxId);
@Test
public void testSetup() throws Exception {
String validResponse = "{\"token\":\"" + token2 + "\"}";
String invalidResponse = "{\"foo\":\"bar\"}";
String invalidTokenResponse = "{\"token\":{\"foo\":\"bar\"}}";
String invalidTokenResponse2 =
"{\"token\":\"" + getRandomString(64) + "\"}";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(validResponse));
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setBody(invalidResponse));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.enqueue(new MockResponse().setBody(invalidTokenResponse));
server.enqueue(new MockResponse().setBody(invalidTokenResponse2));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true);
// valid response with valid token
assertEquals(token2, api.setup(properties));
RecordedRequest request1 = server.takeRequest();
assertEquals("/setup", request1.getPath());
assertEquals("PUT", request1.getMethod());
assertToken(request1, token);
// empty body
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request2 = server.takeRequest();
assertEquals("/setup", request2.getPath());
assertEquals("PUT", request2.getMethod());
assertToken(request2, token);
// invalid response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/setup", request3.getPath());
assertEquals("PUT", request3.getMethod());
assertToken(request3, token);
// 401 response
assertThrows(ApiException.class, () -> api.setup(properties2));
RecordedRequest request4 = server.takeRequest();
assertEquals("/setup", request4.getPath());
assertEquals("PUT", request4.getMethod());
assertToken(request4, token2);
// 500 response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request5 = server.takeRequest();
assertEquals("/setup", request5.getPath());
assertEquals("PUT", request5.getMethod());
assertToken(request5, token);
// invalid json dict token response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request6 = server.takeRequest();
assertEquals("/setup", request6.getPath());
assertEquals("PUT", request6.getMethod());
assertToken(request6, token);
// invalid non-hex string token response
assertThrows(ApiException.class, () -> api.setup(properties));
RecordedRequest request7 = server.takeRequest();
assertEquals("/setup", request7.getPath());
assertEquals("PUT", request7.getMethod());
assertToken(request7, token);
}
@Test
public void testSetupOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(
IllegalArgumentException.class,
() -> api.setup(properties)
);
}
@Test
public void testStatus() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true);
assertTrue(api.checkStatus(properties));
RecordedRequest request1 = server.takeRequest();
assertEquals("/status", request1.getPath());
assertToken(request1, token);
assertThrows(ApiException.class, () -> api.checkStatus(properties2));
RecordedRequest request2 = server.takeRequest();
assertEquals("/status", request2.getPath());
assertToken(request2, token2);
assertFalse(api.checkStatus(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/status", request3.getPath());
assertToken(request3, token);
}
@Test
public void testStatusOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(
IllegalArgumentException.class,
() -> api.checkStatus(properties)
);
}
@Test
public void testWipe() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(204));
server.enqueue(new MockResponse().setResponseCode(200));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
MailboxProperties properties2 =
new MailboxProperties(baseUrl, token2, true);
api.wipeMailbox(properties);
RecordedRequest request1 = server.takeRequest();
assertEquals("/", request1.getPath());
assertEquals("DELETE", request1.getMethod());
assertToken(request1, token);
assertThrows(ApiException.class, () -> api.wipeMailbox(properties2));
RecordedRequest request2 = server.takeRequest();
assertEquals("/", request2.getPath());
assertEquals("DELETE", request2.getMethod());
assertToken(request2, token2);
assertThrows(ApiException.class, () -> api.wipeMailbox(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/", request3.getPath());
assertEquals("DELETE", request3.getMethod());
assertToken(request3, token);
assertThrows(ApiException.class, () -> api.wipeMailbox(properties));
RecordedRequest request4 = server.takeRequest();
assertEquals("/", request4.getPath());
assertEquals("DELETE", request4.getMethod());
assertToken(request4, token);
}
@Test
public void testWipeOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(IllegalArgumentException.class, () ->
api.wipeMailbox(properties));
}
@Test
public void testAddContact() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(409));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// contact gets added as expected
api.addContact(properties, mailboxContact);
RecordedRequest request1 = server.takeRequest();
assertEquals("/contacts", request1.getPath());
assertToken(request1, token);
String expected = "{\"contactId\":" + contactId.getInt() +
",\"token\":\"" + contactToken +
"\",\"inboxId\":\"" + contactInboxId +
"\",\"outboxId\":\"" + contactOutboxId +
"\"}";
assertEquals(expected, request1.getBody().readUtf8());
// request is not successful
assertThrows(ApiException.class, () ->
api.addContact(properties, mailboxContact));
RecordedRequest request2 = server.takeRequest();
assertEquals("/contacts", request2.getPath());
assertToken(request2, token);
// contact already exists
assertThrows(TolerableFailureException.class, () ->
api.addContact(properties, mailboxContact));
RecordedRequest request3 = server.takeRequest();
assertEquals("/contacts", request3.getPath());
assertToken(request3, token);
}
@Test
public void testAddContactOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(IllegalArgumentException.class, () ->
api.addContact(properties, mailboxContact));
}
@Test
public void testDeleteContact() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(205));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(404));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// contact gets deleted as expected
api.deleteContact(properties, contactId);
RecordedRequest request1 = server.takeRequest();
assertEquals("DELETE", request1.getMethod());
assertEquals("/contacts/" + contactId.getInt(), request1.getPath());
assertToken(request1, token);
// request is not returning 200
assertThrows(ApiException.class, () ->
api.deleteContact(properties, contactId));
RecordedRequest request2 = server.takeRequest();
assertEquals("DELETE", request2.getMethod());
assertEquals("/contacts/" + contactId.getInt(), request2.getPath());
assertToken(request2, token);
// request is not authorized
assertThrows(ApiException.class, () ->
api.deleteContact(properties, contactId));
RecordedRequest request3 = server.takeRequest();
assertEquals("DELETE", request3.getMethod());
assertEquals("/contacts/" + contactId.getInt(), request3.getPath());
assertToken(request3, token);
// tolerable 404 not found error
assertThrows(TolerableFailureException.class,
() -> api.deleteContact(properties, contactId));
RecordedRequest request4 = server.takeRequest();
assertEquals("/contacts/" + contactId.getInt(), request4.getPath());
assertEquals("DELETE", request4.getMethod());
assertToken(request4, token);
}
@Test
public void testDeleteContactOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(IllegalArgumentException.class, () ->
api.deleteContact(properties, contactId));
}
@Test
public void testGetContacts() throws Exception {
ContactId contactId2 = getContactId();
String validResponse1 = "{\"contacts\": [" + contactId.getInt() + "] }";
String validResponse2 = "{\"contacts\": [" + contactId.getInt() + ", " +
contactId2.getInt() + "] }";
String invalidResponse1 = "{\"foo\":\"bar\"}";
String invalidResponse2 = "{\"contacts\":{\"foo\":\"bar\"}}";
String invalidResponse3 = "{\"contacts\": [1, 2, \"foo\"] }";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(validResponse1));
server.enqueue(new MockResponse().setBody(validResponse2));
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setBody(invalidResponse1));
server.enqueue(new MockResponse().setBody(invalidResponse2));
server.enqueue(new MockResponse().setBody(invalidResponse3));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// valid response with two contacts
assertEquals(singletonList(contactId), api.getContacts(properties));
RecordedRequest request1 = server.takeRequest();
assertEquals("/contacts", request1.getPath());
assertEquals("GET", request1.getMethod());
assertToken(request1, token);
// valid response with two contacts
List<ContactId> contacts = new ArrayList<>();
contacts.add(contactId);
contacts.add(contactId2);
assertEquals(contacts, api.getContacts(properties));
RecordedRequest request2 = server.takeRequest();
assertEquals("/contacts", request2.getPath());
assertEquals("GET", request2.getMethod());
assertToken(request2, token);
// empty body
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/contacts", request3.getPath());
assertEquals("GET", request3.getMethod());
assertToken(request3, token);
// invalid response: no contacts key
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request4 = server.takeRequest();
assertEquals("/contacts", request4.getPath());
assertEquals("GET", request4.getMethod());
assertToken(request4, token);
// invalid response: no list in contacts
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request5 = server.takeRequest();
assertEquals("/contacts", request5.getPath());
assertEquals("GET", request5.getMethod());
assertToken(request5, token);
// invalid response: list with non-numbers
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request6 = server.takeRequest();
assertEquals("/contacts", request6.getPath());
assertEquals("GET", request6.getMethod());
assertToken(request6, token);
// 401 not authorized
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request7 = server.takeRequest();
assertEquals("/contacts", request7.getPath());
assertEquals("GET", request7.getMethod());
assertToken(request7, token);
// 500 internal server error
assertThrows(ApiException.class, () -> api.getContacts(properties));
RecordedRequest request8 = server.takeRequest();
assertEquals("/contacts", request8.getPath());
assertEquals("GET", request8.getMethod());
assertToken(request8, token);
}
@Test
public void testGetContactsOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(
IllegalArgumentException.class,
() -> api.getContacts(properties)
);
}
@Test
public void testAddFile() throws Exception {
File file = folder.newFile();
byte[] bytes = getRandomBytes(1337);
writeBytes(file, bytes);
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// file gets uploaded as expected
api.addFile(properties, contactInboxId, file);
RecordedRequest request1 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request1.getPath());
assertEquals("POST", request1.getMethod());
assertToken(request1, token);
assertArrayEquals(bytes, request1.getBody().readByteArray());
// request is not successful
assertThrows(ApiException.class, () ->
api.addFile(properties, contactInboxId, file));
RecordedRequest request2 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request2.getPath());
assertEquals("POST", request1.getMethod());
assertToken(request2, token);
// server error
assertThrows(ApiException.class, () ->
api.addFile(properties, contactInboxId, file));
RecordedRequest request3 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request3.getPath());
assertEquals("POST", request1.getMethod());
assertToken(request3, token);
}
@Test
public void testGetFiles() throws Exception {
MailboxFile mailboxFile1 =
new MailboxFile(new MailboxFileId(getRandomId()), 1337);
MailboxFile mailboxFile2 =
new MailboxFile(new MailboxFileId(getRandomId()),
System.currentTimeMillis());
String fileResponse1 =
new ObjectMapper().writeValueAsString(mailboxFile1);
String fileResponse2 =
new ObjectMapper().writeValueAsString(mailboxFile2);
String validResponse1 = "{\"files\": [" + fileResponse1 + "] }";
String validResponse2 = "{\"files\": [" + fileResponse1 + ", " +
fileResponse2 + "] }";
String invalidResponse1 = "{\"files\":\"bar\"}";
String invalidResponse2 = "{\"files\":{\"foo\":\"bar\"}}";
String invalidResponse3 = "{\"files\": [" + fileResponse1 + ", 1] }";
String invalidResponse4 = "{\"contacts\": [ 1, 2 ] }";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(validResponse1));
server.enqueue(new MockResponse().setBody(validResponse2));
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setBody(invalidResponse1));
server.enqueue(new MockResponse().setBody(invalidResponse2));
server.enqueue(new MockResponse().setBody(invalidResponse3));
server.enqueue(new MockResponse().setBody(invalidResponse4));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// valid response with one file
List<MailboxFile> received1 = api.getFiles(properties, contactInboxId);
assertEquals(1, received1.size());
assertEquals(mailboxFile1.name, received1.get(0).name);
assertEquals(mailboxFile1.time, received1.get(0).time);
RecordedRequest request1 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request1.getPath());
assertEquals("GET", request1.getMethod());
assertToken(request1, token);
// valid response with two files
List<MailboxFile> received2 = api.getFiles(properties, contactInboxId);
assertEquals(2, received2.size());
assertEquals(mailboxFile1.name, received2.get(0).name);
assertEquals(mailboxFile1.time, received2.get(0).time);
assertEquals(mailboxFile2.name, received2.get(1).name);
assertEquals(mailboxFile2.time, received2.get(1).time);
RecordedRequest request2 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request1.getPath());
assertEquals("GET", request2.getMethod());
assertToken(request2, token);
// empty body
assertThrows(ApiException.class, () ->
api.getFiles(properties, contactInboxId));
RecordedRequest request3 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request3.getPath());
assertEquals("GET", request3.getMethod());
assertToken(request3, token);
// invalid response: string instead of list
assertThrows(ApiException.class, () ->
api.getFiles(properties, contactInboxId));
RecordedRequest request4 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request4.getPath());
assertEquals("GET", request4.getMethod());
assertToken(request4, token);
// invalid response: object instead of list
assertThrows(ApiException.class, () ->
api.getFiles(properties, contactInboxId));
RecordedRequest request5 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request5.getPath());
assertEquals("GET", request5.getMethod());
assertToken(request5, token);
// invalid response: list with non-objects
assertThrows(ApiException.class, () ->
api.getFiles(properties, contactInboxId));
RecordedRequest request6 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request6.getPath());
assertEquals("GET", request6.getMethod());
assertToken(request6, token);
// no files key in root object
assertThrows(ApiException.class, () ->
api.getFiles(properties, contactInboxId));
RecordedRequest request7 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request7.getPath());
assertEquals("GET", request7.getMethod());
assertToken(request7, token);
// 401 not authorized
assertThrows(ApiException.class, () ->
api.getFiles(properties, contactInboxId));
RecordedRequest request8 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request8.getPath());
assertEquals("GET", request8.getMethod());
assertToken(request8, token);
// 500 internal server error
assertThrows(ApiException.class,
() -> api.getFiles(properties, contactInboxId));
RecordedRequest request9 = server.takeRequest();
assertEquals("/files/" + contactInboxId, request9.getPath());
assertEquals("GET", request9.getMethod());
assertToken(request9, token);
}
@Test
public void testGetFile() throws Exception {
MailboxFileId name = new MailboxFileId(getRandomId());
File file1 = folder.newFile();
File file2 = folder.newFile();
File file3 = folder.newFile();
byte[] bytes = getRandomBytes(1337);
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(new Buffer().write(bytes)));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// file gets downloaded as expected
api.getFile(properties, contactOutboxId, name, file1);
RecordedRequest request1 = server.takeRequest();
assertEquals("/files/" + contactOutboxId + "/" + name,
request1.getPath());
assertEquals("GET", request1.getMethod());
assertToken(request1, token);
assertArrayEquals(bytes, readBytes(file1));
// request is not successful
assertThrows(ApiException.class, () ->
api.getFile(properties, contactOutboxId, name, file2));
RecordedRequest request2 = server.takeRequest();
assertEquals("/files/" + contactOutboxId + "/" + name,
request2.getPath());
assertEquals("GET", request1.getMethod());
assertToken(request2, token);
assertEquals(0, readBytes(file2).length);
// server error
assertThrows(ApiException.class, () ->
api.getFile(properties, contactOutboxId, name, file3));
RecordedRequest request3 = server.takeRequest();
assertEquals("/files/" + contactOutboxId + "/" + name,
request3.getPath());
assertEquals("GET", request1.getMethod());
assertToken(request3, token);
assertEquals(0, readBytes(file3).length);
}
@Test
public void testDeleteFile() throws Exception {
MailboxFileId name = new MailboxFileId(getRandomId());
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setResponseCode(205));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(404));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// file gets deleted as expected
api.deleteFile(properties, contactInboxId, name);
RecordedRequest request1 = server.takeRequest();
assertEquals("DELETE", request1.getMethod());
assertEquals("/files/" + contactInboxId + "/" + name,
request1.getPath());
assertToken(request1, token);
// request is not returning 200
assertThrows(ApiException.class, () ->
api.deleteFile(properties, contactInboxId, name));
RecordedRequest request2 = server.takeRequest();
assertEquals("DELETE", request2.getMethod());
assertEquals("/files/" + contactInboxId + "/" + name,
request2.getPath());
assertToken(request2, token);
// request is not authorized
assertThrows(ApiException.class, () ->
api.deleteFile(properties, contactInboxId, name));
RecordedRequest request3 = server.takeRequest();
assertEquals("DELETE", request3.getMethod());
assertEquals("/files/" + contactInboxId + "/" + name,
request3.getPath());
assertToken(request3, token);
// file not found is tolerable
assertThrows(TolerableFailureException.class, () ->
api.deleteFile(properties, contactInboxId, name));
RecordedRequest request4 = server.takeRequest();
assertEquals("DELETE", request4.getMethod());
assertEquals("/files/" + contactInboxId + "/" + name,
request4.getPath());
assertToken(request4, token);
}
@Test
public void testGetFolders() throws Exception {
MailboxFolderId id1 = new MailboxFolderId(getRandomId());
MailboxFolderId id2 = new MailboxFolderId(getRandomId());
String validResponse1 = "{\"folders\": [ {\"id\": \"" + id1 + "\"} ] }";
String validResponse2 = "{\"folders\": [ {\"id\": \"" + id1 + "\"}, " +
"{ \"id\": \"" + id2 + "\"} ] }";
String invalidResponse1 = "{\"folders\":\"bar\"}";
String invalidResponse2 = "{\"folders\":{\"foo\":\"bar\"}}";
String invalidResponse3 =
"{\"folders\": [ {\"id\": \"" + id1 + "\", 1] }";
String invalidResponse4 = "{\"files\": [ 1, 2 ] }";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody(validResponse1));
server.enqueue(new MockResponse().setBody(validResponse2));
server.enqueue(new MockResponse());
server.enqueue(new MockResponse().setBody(invalidResponse1));
server.enqueue(new MockResponse().setBody(invalidResponse2));
server.enqueue(new MockResponse().setBody(invalidResponse3));
server.enqueue(new MockResponse().setBody(invalidResponse4));
server.enqueue(new MockResponse().setResponseCode(401));
server.enqueue(new MockResponse().setResponseCode(500));
server.start();
String baseUrl = getBaseUrl(server);
MailboxProperties properties =
new MailboxProperties(baseUrl, token, true);
// valid response with one folders
assertEquals(singletonList(id1), api.getFolders(properties));
RecordedRequest request1 = server.takeRequest();
assertEquals("/folders", request1.getPath());
assertEquals("GET", request1.getMethod());
assertToken(request1, token);
// valid response with two folders
assertEquals(Arrays.asList(id1, id2), api.getFolders(properties));
RecordedRequest request2 = server.takeRequest();
assertEquals("/folders", request1.getPath());
assertEquals("GET", request2.getMethod());
assertToken(request2, token);
// empty body
assertThrows(ApiException.class, () -> api.getFolders(properties));
RecordedRequest request3 = server.takeRequest();
assertEquals("/folders", request3.getPath());
assertEquals("GET", request3.getMethod());
assertToken(request3, token);
// invalid response: string instead of list
assertThrows(ApiException.class, () -> api.getFolders(properties));
RecordedRequest request4 = server.takeRequest();
assertEquals("/folders", request4.getPath());
assertEquals("GET", request4.getMethod());
assertToken(request4, token);
// invalid response: object instead of list
assertThrows(ApiException.class, () -> api.getFolders(properties));
RecordedRequest request5 = server.takeRequest();
assertEquals("/folders", request5.getPath());
assertEquals("GET", request5.getMethod());
assertToken(request5, token);
// invalid response: list with non-objects
assertThrows(ApiException.class, () -> api.getFolders(properties));
RecordedRequest request6 = server.takeRequest();
assertEquals("/folders", request6.getPath());
assertEquals("GET", request6.getMethod());
assertToken(request6, token);
// no folders key in root object
assertThrows(ApiException.class, () -> api.getFolders(properties));
RecordedRequest request7 = server.takeRequest();
assertEquals("/folders", request7.getPath());
assertEquals("GET", request7.getMethod());
assertToken(request7, token);
// 401 not authorized
assertThrows(ApiException.class, () -> api.getFolders(properties));
RecordedRequest request8 = server.takeRequest();
assertEquals("/folders", request8.getPath());
assertEquals("GET", request8.getMethod());
assertToken(request8, token);
// 500 internal server error
assertThrows(ApiException.class, () -> api.getFolders(properties));
RecordedRequest request9 = server.takeRequest();
assertEquals("/folders", request9.getPath());
assertEquals("GET", request9.getMethod());
assertToken(request9, token);
}
@Test
public void testGetFoldersOnlyForOwner() {
MailboxProperties properties =
new MailboxProperties("", token, false);
assertThrows(IllegalArgumentException.class, () ->
api.getFolders(properties));
}
private String getBaseUrl(MockWebServer server) {
String baseUrl = server.url("").toString();
return baseUrl.substring(0, baseUrl.length() - 1);
}
private void assertToken(RecordedRequest request, MailboxId token) {
assertNotNull(request.getHeader("Authorization"));
assertEquals("Bearer " + token, request.getHeader("Authorization"));
}
}

View File

@@ -0,0 +1,272 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.WeakSingletonProvider;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.InvalidMailboxIdException;
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.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;
import javax.net.SocketFactory;
import okhttp3.OkHttpClient;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.briarproject.bramble.test.TestUtils.readBytes;
import static org.briarproject.bramble.test.TestUtils.writeBytes;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class MailboxIntegrationTest extends BrambleTestCase {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private final static String URL_BASE = "http://127.0.0.1:8000";
private final static MailboxAuthToken SETUP_TOKEN;
static {
try {
SETUP_TOKEN = MailboxAuthToken.fromString(
"54686973206973206120736574757020746f6b656e20666f722042726961722e");
} catch (InvalidMailboxIdException e) {
throw new IllegalStateException();
}
}
private static final OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(SocketFactory.getDefault())
.connectTimeout(60_000, MILLISECONDS)
.build();
private static final WeakSingletonProvider<OkHttpClient>
httpClientProvider =
new WeakSingletonProvider<OkHttpClient>() {
@Override
@Nonnull
public OkHttpClient createInstance() {
return client;
}
};
private final static MailboxApiImpl api =
new MailboxApiImpl(httpClientProvider);
// needs to be static to keep values across different tests
private static MailboxProperties ownerProperties;
/**
* Called before each test to make sure the mailbox is setup once
* before starting with individual tests.
* {@link BeforeClass} needs to be static, so we can't use the API class.
*/
@Before
public void ensureSetup() throws IOException, ApiException {
// Skip this test unless it's explicitly enabled in the environment
assumeTrue(isOptionalTestEnabled(MailboxIntegrationTest.class));
if (ownerProperties != null) return;
MailboxProperties setupProperties =
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
MailboxAuthToken ownerToken = api.setup(setupProperties);
ownerProperties = new MailboxProperties(URL_BASE, ownerToken, true);
}
@AfterClass
// we can't test wiping as a regular test as it stops the mailbox
public static void wipe() throws IOException, ApiException {
if (!isOptionalTestEnabled(MailboxIntegrationTest.class)) return;
api.wipeMailbox(ownerProperties);
// check doesn't work anymore
assertThrows(ApiException.class, () ->
api.checkStatus(ownerProperties));
// new setup doesn't work as mailbox is stopping
MailboxProperties setupProperties =
new MailboxProperties(URL_BASE, SETUP_TOKEN, true);
assertThrows(ApiException.class, () -> api.setup(setupProperties));
}
@Test
public void testStatus() throws Exception {
assertTrue(api.checkStatus(ownerProperties));
}
@Test
public void testContactApi() throws Exception {
ContactId contactId1 = new ContactId(1);
ContactId contactId2 = new ContactId(2);
MailboxContact mailboxContact1 = getMailboxContact(contactId1);
MailboxContact mailboxContact2 = getMailboxContact(contactId2);
// no contacts initially
assertEquals(emptyList(), api.getContacts(ownerProperties));
// added contact gets returned
api.addContact(ownerProperties, mailboxContact1);
assertEquals(singletonList(contactId1),
api.getContacts(ownerProperties));
// second contact also gets returned
api.addContact(ownerProperties, mailboxContact2);
assertEquals(Arrays.asList(contactId1, contactId2),
api.getContacts(ownerProperties));
// after both contacts get deleted, the list is empty again
api.deleteContact(ownerProperties, contactId1);
api.deleteContact(ownerProperties, contactId2);
assertEquals(emptyList(), api.getContacts(ownerProperties));
// deleting again is tolerable
assertThrows(TolerableFailureException.class,
() -> api.deleteContact(ownerProperties, contactId2));
}
@Test
public void testFileManagementApi() throws Exception {
// add contact, so we can leave each other files
ContactId contactId = new ContactId(1);
MailboxContact contact = getMailboxContact(contactId);
MailboxProperties contactProperties = new MailboxProperties(
ownerProperties.getBaseUrl(), contact.token, false);
api.addContact(ownerProperties, contact);
// upload a file for our contact
File file1 = folder.newFile();
byte[] bytes1 = getRandomBytes(2048);
writeBytes(file1, bytes1);
api.addFile(ownerProperties, contact.inboxId, file1);
// contact checks files
List<MailboxFile> files1 =
api.getFiles(contactProperties, contact.inboxId);
assertEquals(1, files1.size());
MailboxFileId fileName1 = files1.get(0).name;
// owner can't check files
assertThrows(ApiException.class, () ->
api.getFiles(ownerProperties, contact.inboxId));
// contact downloads file
File file1downloaded = folder.newFile();
api.getFile(contactProperties, contact.inboxId, fileName1,
file1downloaded);
assertArrayEquals(bytes1, readBytes(file1downloaded));
// owner can't download file, even if knowing name
File file1forbidden = folder.newFile();
assertThrows(ApiException.class, () -> api.getFile(ownerProperties,
contact.inboxId, fileName1, file1forbidden));
assertEquals(0, file1forbidden.length());
// owner can't delete file
assertThrows(ApiException.class, () ->
api.deleteFile(ownerProperties, contact.inboxId, fileName1));
// contact deletes file
api.deleteFile(contactProperties, contact.inboxId, fileName1);
assertEquals(0,
api.getFiles(contactProperties, contact.inboxId).size());
// contact uploads two files for the owner
File file2 = folder.newFile();
File file3 = folder.newFile();
byte[] bytes2 = getRandomBytes(2048);
byte[] bytes3 = getRandomBytes(1024);
writeBytes(file2, bytes2);
writeBytes(file3, bytes3);
api.addFile(contactProperties, contact.outboxId, file2);
api.addFile(contactProperties, contact.outboxId, file3);
// owner checks folders with available files
List<MailboxFolderId> folders = api.getFolders(ownerProperties);
assertEquals(singletonList(contact.outboxId), folders);
// owner lists files in contact's outbox
List<MailboxFile> files2 =
api.getFiles(ownerProperties, contact.outboxId);
assertEquals(2, files2.size());
MailboxFileId file2name = files2.get(0).name;
MailboxFileId file3name = files2.get(1).name;
// contact can't list files in contact's outbox
assertThrows(ApiException.class, () ->
api.getFiles(contactProperties, contact.outboxId));
// owner downloads both files from contact's outbox
File file2downloaded = folder.newFile();
File file3downloaded = folder.newFile();
api.getFile(ownerProperties, contact.outboxId, file2name,
file2downloaded);
api.getFile(ownerProperties, contact.outboxId, file3name,
file3downloaded);
byte[] downloadedBytes2 = readBytes(file2downloaded);
byte[] downloadedBytes3 = readBytes(file3downloaded);
// file order is preserved (sorted by time),
// so we know what file is which
assertArrayEquals(bytes2, downloadedBytes2);
assertArrayEquals(bytes3, downloadedBytes3);
// contact can't download files again, even if knowing name
File file2forbidden = folder.newFile();
File file3forbidden = folder.newFile();
assertThrows(ApiException.class, () -> api.getFile(contactProperties,
contact.outboxId, file2name, file2forbidden));
assertThrows(ApiException.class, () -> api.getFile(contactProperties,
contact.outboxId, file3name, file3forbidden));
assertEquals(0, file1forbidden.length());
assertEquals(0, file2forbidden.length());
// contact can't delete files in outbox
assertThrows(ApiException.class, () ->
api.deleteFile(contactProperties, contact.outboxId, file2name));
assertThrows(ApiException.class, () ->
api.deleteFile(contactProperties, contact.outboxId, file3name));
// owner deletes files
api.deleteFile(ownerProperties, contact.outboxId, file2name);
api.deleteFile(ownerProperties, contact.outboxId, file3name);
assertEquals(emptyList(),
api.getFiles(ownerProperties, contact.outboxId));
assertEquals(emptyList(), api.getFolders(ownerProperties));
// deleting a non-existent file is tolerable
assertThrows(TolerableFailureException.class, () ->
api.deleteFile(ownerProperties, contact.outboxId, file3name));
// owner deletes contact again to leave clean state for other tests
api.deleteContact(ownerProperties, contactId);
assertEquals(emptyList(), api.getContacts(ownerProperties));
}
private MailboxContact getMailboxContact(ContactId contactId) {
MailboxAuthToken authToken = new MailboxAuthToken(getRandomId());
MailboxFolderId inboxId = new MailboxFolderId(getRandomId());
MailboxFolderId outboxId = new MailboxFolderId(getRandomId());
return new MailboxContact(contactId, authToken, inboxId, outboxId);
}
}

View File

@@ -0,0 +1,196 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.crypto.CryptoComponent;
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.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.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations;
import org.junit.Test;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
private final Executor executor = new ImmediateExecutor();
private final TransactionManager db =
context.mock(TransactionManager.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final Clock clock = context.mock(Clock.class);
private final MailboxApi api = context.mock(MailboxApi.class);
private final MailboxSettingsManager mailboxSettingsManager =
context.mock(MailboxSettingsManager.class);
private final MailboxPairingTaskFactory factory =
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api,
mailboxSettingsManager);
private final String onion = getRandomString(56);
private final byte[] onionBytes = getRandomBytes(32);
private final String onionAddress = "http://" + onion + ".onion";
private final MailboxAuthToken setupToken =
new MailboxAuthToken(getRandomId());
private final MailboxAuthToken ownerToken =
new MailboxAuthToken(getRandomId());
private final String validPayload = getValidPayload();
private final long time = System.currentTimeMillis();
private final MailboxProperties setupProperties =
new MailboxProperties(onionAddress, setupToken, true);
private final MailboxProperties ownerProperties =
new MailboxProperties(onionAddress, ownerToken, true);
@Test
public void testInitialQrCodeReceivedState() {
MailboxPairingTask task =
factory.createPairingTask(getRandomString(42));
task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.QrCodeReceived)
);
}
@Test
public void testInvalidQrCode() {
MailboxPairingTask task1 =
factory.createPairingTask(getRandomString(42));
task1.run();
task1.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
);
String goodLength = "00" + getRandomString(63);
MailboxPairingTask task2 = factory.createPairingTask(goodLength);
task2.run();
task2.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
);
}
@Test
public void testSuccessfulPairing() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).encodeOnionAddress(onionBytes);
will(returnValue(onion));
oneOf(api).setup(with(matches(setupProperties)));
will(returnValue(ownerToken));
oneOf(clock).currentTimeMillis();
will(returnValue(time));
}});
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
with(txn), with(matches(ownerProperties)));
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, time);
}});
AtomicInteger i = new AtomicInteger(0);
MailboxPairingTask task = factory.createPairingTask(validPayload);
task.addObserver(state -> {
if (i.get() == 0) {
assertEquals(MailboxPairingState.QrCodeReceived.class,
state.getClass());
} else if (i.get() == 1) {
assertEquals(MailboxPairingState.Pairing.class,
state.getClass());
} else if (i.get() == 2) {
assertEquals(MailboxPairingState.Paired.class,
state.getClass());
} else fail("Unexpected change of state " + state.getClass());
i.getAndIncrement();
});
task.run();
}
@Test
public void testAlreadyPaired() throws Exception {
testApiException(new MailboxApi.MailboxAlreadyPairedException(),
MailboxPairingState.MailboxAlreadyPaired.class);
}
@Test
public void testMailboxApiException() throws Exception {
testApiException(new MailboxApi.ApiException(),
MailboxPairingState.UnexpectedError.class);
}
@Test
public void testApiIOException() throws Exception {
testApiException(new IOException(),
MailboxPairingState.ConnectionError.class);
}
private void testApiException(Exception e,
Class<? extends MailboxPairingState> s) throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).encodeOnionAddress(onionBytes);
will(returnValue(onion));
oneOf(api).setup(with(matches(setupProperties)));
will(throwException(e));
}});
MailboxPairingTask task = factory.createPairingTask(validPayload);
task.run();
task.addObserver(state -> assertEquals(state.getClass(), s));
}
@Test
public void testDbException() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).encodeOnionAddress(onionBytes);
will(returnValue(onion));
oneOf(api).setup(with(matches(setupProperties)));
will(returnValue(ownerToken));
oneOf(clock).currentTimeMillis();
will(returnValue(time));
}});
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(mailboxSettingsManager).setOwnMailboxProperties(
with(txn), with(matches(ownerProperties)));
will(throwException(new DbException()));
}});
MailboxPairingTask task = factory.createPairingTask(validPayload);
task.run();
task.addObserver(state -> assertEquals(state.getClass(),
MailboxPairingState.UnexpectedError.class));
}
private String getValidPayload() {
byte[] payloadBytes = ByteBuffer.allocate(65)
.put((byte) 32) // 1
.put(onionBytes) // 32
.put(setupToken.getBytes()) // 32
.array();
//noinspection CharsetObjectCanBeUsed
return new String(payloadBytes, Charset.forName("ISO-8859-1"));
}
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
return new PredicateMatcher<>(MailboxProperties.class, p1 ->
p1.getAuthToken().equals(p2.getAuthToken()) &&
p1.getBaseUrl().equals(p2.getBaseUrl()) &&
p1.isOwner() == p2.isOwner());
}
}

View File

@@ -0,0 +1,228 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.Transaction;
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.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.Random;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ATTEMPTS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_ATTEMPT;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_LAST_SUCCESS;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_ONION;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_KEY_TOKEN;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_NAMESPACE;
import static org.briarproject.bramble.mailbox.MailboxSettingsManagerImpl.SETTINGS_UPLOADS_NAMESPACE;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
private final SettingsManager settingsManager =
context.mock(SettingsManager.class);
private final MailboxSettingsManager manager =
new MailboxSettingsManagerImpl(settingsManager);
private final Random random = new Random();
private final String onion = getRandomString(64);
private final MailboxAuthToken token = new MailboxAuthToken(getRandomId());
private final ContactId contactId1 = new ContactId(random.nextInt());
private final ContactId contactId2 = new ContactId(random.nextInt());
private final ContactId contactId3 = new ContactId(random.nextInt());
private final long now = System.currentTimeMillis();
private final long lastAttempt = now - 1234;
private final long lastSuccess = now - 2345;
private final int attempts = 123;
@Test
public void testReturnsNullPropertiesIfSettingsAreEmpty() throws Exception {
Transaction txn = new Transaction(null, true);
Settings emptySettings = new Settings();
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(emptySettings));
}});
assertNull(manager.getOwnMailboxProperties(txn));
}
@Test
public void testReturnsProperties() throws Exception {
Transaction txn = new Transaction(null, true);
Settings settings = new Settings();
settings.put(SETTINGS_KEY_ONION, onion);
settings.put(SETTINGS_KEY_TOKEN, token.toString());
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(settings));
}});
MailboxProperties properties = manager.getOwnMailboxProperties(txn);
assertNotNull(properties);
assertEquals(onion, properties.getBaseUrl());
assertEquals(token, properties.getAuthToken());
assertTrue(properties.isOwner());
}
@Test
public void testStoresProperties() throws Exception {
Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings();
expectedSettings.put(SETTINGS_KEY_ONION, onion);
expectedSettings.put(SETTINGS_KEY_TOKEN, token.toString());
MailboxProperties properties =
new MailboxProperties(onion, token, true);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.setOwnMailboxProperties(txn, properties);
}
@Test
public void testReturnsDefaultStatusIfSettingsAreEmpty() throws Exception {
Transaction txn = new Transaction(null, true);
Settings emptySettings = new Settings();
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(emptySettings));
}});
MailboxStatus status = manager.getOwnMailboxStatus(txn);
assertEquals(-1, status.getTimeOfLastAttempt());
assertEquals(-1, status.getTimeOfLastSuccess());
assertEquals(0, status.getAttemptsSinceSuccess());
}
@Test
public void testReturnsStatus() throws Exception {
Transaction txn = new Transaction(null, true);
Settings settings = new Settings();
settings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt);
settings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess);
settings.putInt(SETTINGS_KEY_ATTEMPTS, attempts);
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(settings));
}});
MailboxStatus status = manager.getOwnMailboxStatus(txn);
assertEquals(lastAttempt, status.getTimeOfLastAttempt());
assertEquals(lastSuccess, status.getTimeOfLastSuccess());
assertEquals(attempts, status.getAttemptsSinceSuccess());
}
@Test
public void testRecordsSuccess() throws Exception {
Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.recordSuccessfulConnection(txn, now);
}
@Test
public void testRecordsFailureOnFirstAttempt() throws Exception {
testRecordsFailure(new Settings(), 0);
}
@Test
public void testRecordsFailureOnLaterAttempt() throws Exception {
Settings oldSettings = new Settings();
oldSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, lastAttempt);
oldSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, lastSuccess);
oldSettings.putInt(SETTINGS_KEY_ATTEMPTS, attempts);
testRecordsFailure(oldSettings, attempts);
}
private void testRecordsFailure(Settings oldSettings, int oldAttempts)
throws Exception {
Transaction txn = new Transaction(null, false);
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, oldAttempts + 1);
context.checking(new Expectations() {{
oneOf(settingsManager).getSettings(txn, SETTINGS_NAMESPACE);
will(returnValue(oldSettings));
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.recordFailedConnectionAttempt(txn, now);
}
@Test
public void testGettingPendingUploads() throws Exception {
Transaction txn = new Transaction(null, true);
Settings settings = new Settings();
settings.put(String.valueOf(contactId1.getInt()), onion);
settings.put(String.valueOf(contactId2.getInt()), token.toString());
settings.put(String.valueOf(contactId3.getInt()), "");
context.checking(new Expectations() {{
exactly(4).of(settingsManager)
.getSettings(txn, SETTINGS_UPLOADS_NAMESPACE);
will(returnValue(settings));
}});
String filename1 = manager.getPendingUpload(txn, contactId1);
assertEquals(onion, filename1);
String filename2 = manager.getPendingUpload(txn, contactId2);
assertNotNull(filename2);
assertEquals(token.toString(), filename2);
String filename3 = manager.getPendingUpload(txn, contactId3);
assertNull(filename3);
String filename4 =
manager.getPendingUpload(txn, new ContactId(random.nextInt()));
assertNull(filename4);
}
@Test
public void testSettingPendingUploads() throws Exception {
Transaction txn = new Transaction(null, false);
// setting a pending upload stores expected settings
Settings expectedSettings1 = new Settings();
expectedSettings1.put(String.valueOf(contactId1.getInt()), onion);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings1,
SETTINGS_UPLOADS_NAMESPACE);
}});
manager.setPendingUpload(txn, contactId1, onion);
// nulling a pending upload empties stored settings
Settings expectedSettings2 = new Settings();
expectedSettings2.put(String.valueOf(contactId2.getInt()), "");
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings2,
SETTINGS_UPLOADS_NAMESPACE);
}});
manager.setPendingUpload(txn, contactId2, null);
}
}

View File

@@ -12,10 +12,8 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Test;
import java.util.Arrays;
@@ -24,13 +22,10 @@ import java.util.concurrent.Executors;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
public class PluginManagerImplTest extends BrambleTestCase {
public class PluginManagerImplTest extends BrambleMockTestCase {
@Test
public void testStartAndStop() throws Exception {
Mockery context = new Mockery() {{
setThreadingPolicy(new Synchroniser());
}};
Executor ioExecutor = Executors.newSingleThreadExecutor();
EventBus eventBus = context.mock(EventBus.class);
PluginConfig pluginConfig = context.mock(PluginConfig.class);
@@ -116,7 +111,5 @@ public class PluginManagerImplTest extends BrambleTestCase {
// Two plugins should be started and stopped
p.startService();
p.stopService();
context.assertIsSatisfied();
}
}

View File

@@ -25,7 +25,7 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Before;
import org.junit.Test;
@@ -69,7 +69,7 @@ public class PollerImplTest extends BrambleMockTestCase {
private PollerImpl poller;
public PollerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
random = context.mock(SecureRandom.class);
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
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.DEFAULT_OBFS4_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.MEEK_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_OBFS4_BRIDGES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class CircumventionProviderTest extends BrambleTestCase {
private final CircumventionProvider provider =
new CircumventionProviderImpl();
@Test
public void testInvariants() {
Set<String> blocked = new HashSet<>(asList(BLOCKED));
Set<String> bridges = new HashSet<>(asList(BRIDGES));
Set<String> defaultObfs4Bridges =
new HashSet<>(asList(DEFAULT_OBFS4_BRIDGES));
Set<String> nonDefaultObfs4Bridges =
new HashSet<>(asList(NON_DEFAULT_OBFS4_BRIDGES));
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
// BRIDGES should be a subset of BLOCKED
assertTrue(blocked.containsAll(bridges));
// BRIDGES should be the union of the bridge type sets
Set<String> union = new HashSet<>(defaultObfs4Bridges);
union.addAll(nonDefaultObfs4Bridges);
union.addAll(meekBridges);
assertEquals(bridges, union);
// The bridge type sets should not overlap
assertEmptyIntersection(defaultObfs4Bridges, nonDefaultObfs4Bridges);
assertEmptyIntersection(defaultObfs4Bridges, meekBridges);
assertEmptyIntersection(nonDefaultObfs4Bridges, meekBridges);
}
@Test
public void testGetBestBridgeType() {
for (String country : DEFAULT_OBFS4_BRIDGES) {
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType(country));
}
for (String country : NON_DEFAULT_OBFS4_BRIDGES) {
assertEquals(NON_DEFAULT_OBFS4,
provider.getBestBridgeType(country));
}
for (String country : MEEK_BRIDGES) {
assertEquals(MEEK, provider.getBestBridgeType(country));
}
assertEquals(DEFAULT_OBFS4, provider.getBestBridgeType("ZZ"));
}
private <T> void assertEmptyIntersection(Set<T> a, Set<T> b) {
Set<T> intersection = new HashSet<>(a);
intersection.retainAll(b);
assertTrue(intersection.isEmpty());
}
}

View File

@@ -26,12 +26,22 @@ public class TestFeatureFlagModule {
}
@Override
public boolean shouldEnableTransferData() {
public boolean shouldEnableMailbox() {
return true;
}
@Override
public boolean shouldEnableShareAppViaOfflineHotspot() {
public boolean shouldEnablePrivateGroupsInCore() {
return true;
}
@Override
public boolean shouldEnableForumsInCore() {
return true;
}
@Override
public boolean shouldEnableBlogsInCore() {
return true;
}
};

View File

@@ -1,19 +1,17 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.StreamDecrypter;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.junit.Assert.assertEquals;
public class StreamReaderImplTest extends BrambleTestCase {
public class StreamReaderImplTest extends BrambleMockTestCase {
@Test
public void testEmptyFramesAreSkipped() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -30,13 +28,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
assertEquals(0, r.read()); // Read another byte
assertEquals(-1, r.read()); // Skip the second empty frame, reach EOF
assertEquals(-1, r.read()); // Still at EOF
context.assertIsSatisfied();
r.close();
}
@Test
public void testEmptyFramesAreSkippedWithBuffer() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -56,13 +52,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
assertEquals(-1, r.read(buf));
// Still at EOF
assertEquals(-1, r.read(buf));
context.assertIsSatisfied();
r.close();
}
@Test
public void testMultipleReadsPerFrame() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -78,13 +72,11 @@ public class StreamReaderImplTest extends BrambleTestCase {
assertEquals(MAX_PAYLOAD_LENGTH / 2, r.read(buf));
// Reach EOF
assertEquals(-1, r.read(buf, 0, buf.length));
context.assertIsSatisfied();
r.close();
}
@Test
public void testMultipleReadsPerFrameWithOffsets() throws Exception {
Mockery context = new Mockery();
StreamDecrypter decrypter = context.mock(StreamDecrypter.class);
context.checking(new Expectations() {{
oneOf(decrypter).readFrame(with(any(byte[].class)));
@@ -102,7 +94,6 @@ public class StreamReaderImplTest extends BrambleTestCase {
MAX_PAYLOAD_LENGTH / 2));
// Reach EOF
assertEquals(-1, r.read(buf, 0, buf.length));
context.assertIsSatisfied();
r.close();
}
}

View File

@@ -1,19 +1,17 @@
package org.briarproject.bramble.transport;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.junit.Assert.assertEquals;
public class StreamWriterImplTest extends BrambleTestCase {
public class StreamWriterImplTest extends BrambleMockTestCase {
@Test
public void testCloseWithoutWritingWritesFinalFrame() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
context.checking(new Expectations() {{
// Write an empty final frame
@@ -24,13 +22,11 @@ public class StreamWriterImplTest extends BrambleTestCase {
}});
StreamWriterImpl w = new StreamWriterImpl(encrypter);
w.close();
context.assertIsSatisfied();
}
@Test
public void testFlushWithoutBufferedDataWritesFrameAndFlushes()
throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -51,13 +47,11 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testFlushWithBufferedDataWritesFrameAndFlushes()
throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -79,12 +73,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testSingleByteWritesWriteFullFrame() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -103,12 +95,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testMultiByteWritesWriteFullFrames() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -134,12 +124,10 @@ public class StreamWriterImplTest extends BrambleTestCase {
oneOf(encrypter).flush();
}});
w.close();
context.assertIsSatisfied();
}
@Test
public void testLargeMultiByteWriteWritesFullFrames() throws Exception {
Mockery context = new Mockery();
StreamEncrypter encrypter = context.mock(StreamEncrypter.class);
StreamWriterImpl w = new StreamWriterImpl(encrypter);
context.checking(new Expectations() {{
@@ -157,6 +145,5 @@ public class StreamWriterImplTest extends BrambleTestCase {
w.write(b);
// There should be one byte left in the buffer
w.close();
context.assertIsSatisfied();
}
}

View File

@@ -393,6 +393,76 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
assertNull(transportKeyManager.getStreamContext(txn, tag));
}
@Test
public void testGetStreamContextOnlyAndMarkTag() throws Exception {
boolean alice = random.nextBoolean();
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
Transaction txn = new Transaction(null, false);
// Keep a copy of the tags
List<byte[]> tags = new ArrayList<>();
context.checking(new Expectations() {{
oneOf(transportCrypto).deriveRotationKeys(transportId, rootKey,
1000, alice, true);
will(returnValue(transportKeys));
// Get the current time (the start of time period 1000)
oneOf(clock).currentTimeMillis();
will(returnValue(timePeriodLength * 1000));
// Encode the tags (3 sets)
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
exactly(3).of(transportCrypto).encodeTag(
with(any(byte[].class)), with(tagKey),
with(PROTOCOL_VERSION), with(i));
will(new EncodeTagAction(tags));
}
// Updated the transport keys (the keys are unaffected)
oneOf(transportCrypto).updateTransportKeys(transportKeys, 1000);
will(returnValue(transportKeys));
// Save the keys
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
will(returnValue(keySetId));
// Encode a new tag after sliding the window
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
with(tagKey), with(PROTOCOL_VERSION),
with((long) REORDERING_WINDOW_SIZE));
will(new EncodeTagAction(tags));
// Save the reordering window (previous time period, base 1)
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
1, new byte[REORDERING_WINDOW_SIZE / 8]);
}});
// The timestamp is at the start of time period 1000
long timestamp = timePeriodLength * 1000;
assertEquals(keySetId, transportKeyManager.addRotationKeys(
txn, contactId, rootKey, timestamp, alice, true));
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
// Use the first tag (previous time period, stream number 0)
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
byte[] tag = tags.get(0);
// Repeated request should return same stream context
StreamContext ctx = transportKeyManager.getStreamContextOnly(txn, tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertEquals(tagKey, ctx.getTagKey());
assertEquals(headerKey, ctx.getHeaderKey());
assertEquals(0L, ctx.getStreamNumber());
ctx = transportKeyManager.getStreamContextOnly(txn, tag);
assertNotNull(ctx);
assertEquals(contactId, ctx.getContactId());
assertEquals(transportId, ctx.getTransportId());
assertEquals(tagKey, ctx.getTagKey());
assertEquals(headerKey, ctx.getHeaderKey());
assertEquals(0L, ctx.getStreamNumber());
// Then mark tag as recognised
transportKeyManager.markTagAsRecognised(txn, tag);
// Another tag should have been encoded
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
// Finally ensure the used tag is not recognised again
assertNull(transportKeyManager.getStreamContextOnly(txn, tag));
}
@Test
public void testKeysAreUpdatedToCurrentPeriod() throws Exception {
TransportKeys transportKeys = createTransportKeys(1000, 0, true);

View File

@@ -1,6 +1,9 @@
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.fasterxml.jackson.core:jackson-core:2.13.0:jackson-core-2.13.0.jar:348bc59b348df2e807b356f1d62d2afb41a974073328abc773eb0932b855d2c8',
'com.fasterxml.jackson.core:jackson-databind:2.13.0:jackson-databind-2.13.0.jar:9c826d27176268777adcf97e1c6e2051c7e33a7aaa2c370c2e8c6077fd9da3f4',
'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-compiler:2.33:dagger-compiler-2.33.jar:aa8a0d8370c578fd6999802d0d90b9829377a46d2c1141e11b8f737970e7155e',
@@ -15,6 +18,11 @@ dependencyVerification {
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'com.squareup.okhttp3:mockwebserver:4.9.3:mockwebserver-4.9.3.jar:9c8c581c29f22f877a35d11380462f75bb24bf1886204fe835ee695594a2784e',
'com.squareup.okhttp3:okhttp:3.12.13:okhttp-3.12.13.jar:508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82',
'com.squareup.okhttp3:okhttp:4.9.3:okhttp-4.9.3.jar:93ecd6cba19d87dccfe566ec848d91aae799e3cf16c00709358ea69bd9227219',
'com.squareup.okio:okio:1.15.0:okio-1.15.0.jar:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2',
'com.squareup.okio:okio:2.8.0:okio-jvm-2.8.0.jar:4496b06e73982fcdd8a5393f46e5df2ce2fa4465df5895454cac68a32f09bbc8',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
@@ -26,7 +34,7 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
'org.bouncycastle:bcprov-jdk15to18:1.70:bcprov-jdk15to18-1.70.jar:7df4c54f29ce2dd616dc3b198ca4db3dfcc79e3cb397c084a0aff97b85c0bf38',
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
'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',
@@ -38,7 +46,11 @@ dependencyVerification {
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10:kotlin-stdlib-common-1.4.10.jar:4681f2d436a68c7523595d84ed5758e1382f9da0f67c91e6a848690d711274fe',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20:kotlin-stdlib-common-1.4.20.jar:a7112c9b3cefee418286c9c9372f7af992bd1e6e030691d52f60cb36dbec8320',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10:kotlin-stdlib-jdk7-1.4.10.jar:f9566380c08722c780ce33ceee23e98ddf765ca98fabd3e2fabae7975c8d232b',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10:kotlin-stdlib-jdk8-1.4.10.jar:39b7a9442d7a3865e0f4a732c56c1d5da0e11ffb3bb82a461d32deb0c0ca7673',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.10:kotlin-stdlib-1.4.10.jar:01ecb09782c042b931c1839acf21a188340b295d05400afd6e3415d4475b8daa',
'org.jetbrains.kotlin:kotlin-stdlib:1.4.20:kotlin-stdlib-1.4.20.jar:b8ab1da5cdc89cb084d41e1f28f20a42bd431538642a5741c52bbfae3fa3e656',
'org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0:kotlinx-metadata-jvm-0.1.0.jar:9753bb39efef35957c5c15df9a3cb769aabf2cdfa74b47afcb7760e5146be3b5',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',

View File

@@ -17,8 +17,8 @@ dependencies {
def jna_version = '4.5.2'
implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version"
tor 'org.briarproject:tor:0.3.5.15'
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
tor "org.briarproject:tor:$tor_version"
tor "org.briarproject:obfs4proxy:$obfs4proxy_version@zip"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -27,7 +27,6 @@ dependencies {
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-legacy:$jmock_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
}

View File

@@ -35,12 +35,15 @@ abstract class JavaTorPlugin extends TorPlugin {
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory) {
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory);
maxLatency, maxIdleTime, torDirectory, torSocksPort,
torControlPort);
}
@Override

View File

@@ -35,12 +35,15 @@ class UnixTorPlugin extends JavaTorPlugin {
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory) {
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory);
maxLatency, maxIdleTime, torDirectory, torSocksPort,
torControlPort);
}
@Override

View File

@@ -1,6 +1,7 @@
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;
@@ -9,7 +10,9 @@ 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;
@@ -54,6 +57,9 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
private final BatteryManager batteryManager;
private final Clock clock;
private final File torDirectory;
private int torSocksPort;
private int torControlPort;
private final CryptoComponent crypto;
@Inject
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
@@ -67,7 +73,10 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
@TorDirectory File torDirectory) {
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort,
CryptoComponent crypto) {
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.networkManager = networkManager;
@@ -80,6 +89,9 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
this.batteryManager = batteryManager;
this.clock = clock;
this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort;
this.crypto = crypto;
}
@Override
@@ -120,12 +132,14 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
TorRendezvousCrypto torRendezvousCrypto =
new TorRendezvousCryptoImpl(crypto);
UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory);
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -2,15 +2,18 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.BrambleCoreIntegrationTestEagerSingletons;
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.BackoffFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
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.plugin.tor.CircumventionProvider.BridgeType;
import org.briarproject.bramble.test.BrambleJavaIntegrationTestComponent;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.DaggerBrambleJavaIntegrationTestComponent;
@@ -33,9 +36,14 @@ import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT;
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.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
@@ -54,14 +62,21 @@ public class BridgeTest extends BrambleTestCase {
.injectEagerSingletons(component);
// Share a failure counter among all the test instances
AtomicInteger failures = new AtomicInteger(0);
List<String> bridges =
component.getCircumventionProvider().getBridges(false);
List<Params> states = new ArrayList<>(bridges.size());
for (String bridge : bridges) states.add(new Params(bridge, failures));
CircumventionProvider provider = component.getCircumventionProvider();
List<Params> states = new ArrayList<>();
for (String bridge : provider.getBridges(DEFAULT_OBFS4)) {
states.add(new Params(bridge, DEFAULT_OBFS4, failures, false));
}
for (String bridge : provider.getBridges(NON_DEFAULT_OBFS4)) {
states.add(new Params(bridge, NON_DEFAULT_OBFS4, failures, false));
}
for (String bridge : provider.getBridges(MEEK)) {
states.add(new Params(bridge, MEEK, failures, true));
}
return states;
}
private final static long TIMEOUT = SECONDS.toMillis(60);
private final static long TIMEOUT = MINUTES.toMillis(5);
private final static int NUM_FAILURES_ALLOWED = 1;
private final static Logger LOG = getLogger(BridgeTest.class.getName());
@@ -86,16 +101,16 @@ public class BridgeTest extends BrambleTestCase {
BackoffFactory backoffFactory;
@Inject
Clock clock;
@Inject
CryptoComponent crypto;
private final File torDir = getTestDirectory();
private final String bridge;
private final AtomicInteger failures;
private final Params params;
private UnixTorPluginFactory factory;
public BridgeTest(Params params) {
bridge = params.bridge;
failures = params.failures;
this.params = params;
}
@Before
@@ -115,6 +130,7 @@ public class BridgeTest extends BrambleTestCase {
LocationUtils locationUtils = () -> "US";
SocketFactory torSocketFactory = SocketFactory.getDefault();
@NotNullByDefault
CircumventionProvider bridgeProvider = new CircumventionProvider() {
@Override
public boolean isTorProbablyBlocked(String countryCode) {
@@ -127,19 +143,20 @@ public class BridgeTest extends BrambleTestCase {
}
@Override
public boolean needsMeek(String countryCode) {
return false;
public BridgeType getBestBridgeType(String countryCode) {
return params.bridgeType;
}
@Override
public List<String> getBridges(boolean useMeek) {
return singletonList(bridge);
public List<String> getBridges(BridgeType bridgeType) {
return singletonList(params.bridge);
}
};
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, bridgeProvider,
batteryManager, clock, torDir);
batteryManager, clock, torDir, DEFAULT_SOCKS_PORT,
DEFAULT_CONTROL_PORT, crypto);
}
@After
@@ -154,7 +171,7 @@ public class BridgeTest extends BrambleTestCase {
assertNotNull(duplexPlugin);
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
LOG.warning("Testing " + bridge);
LOG.warning("Testing " + params.bridge);
try {
plugin.start();
long start = clock.currentTimeMillis();
@@ -164,8 +181,11 @@ public class BridgeTest extends BrambleTestCase {
}
if (plugin.getState() != ACTIVE) {
LOG.warning("Could not connect to Tor within timeout");
if (failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
fail(failures.get() + " bridges are unreachable");
if (params.failures.incrementAndGet() > NUM_FAILURES_ALLOWED) {
fail(params.failures.get() + " bridges are unreachable");
}
if (params.mustSucceed) {
fail("essential bridge is unreachable");
}
}
} finally {
@@ -176,11 +196,16 @@ public class BridgeTest extends BrambleTestCase {
private static class Params {
private final String bridge;
private final BridgeType bridgeType;
private final AtomicInteger failures;
private final boolean mustSucceed;
private Params(String bridge, AtomicInteger failures) {
private Params(String bridge, BridgeType bridgeType,
AtomicInteger failures, boolean mustSucceed) {
this.bridge = bridge;
this.bridgeType = bridgeType;
this.failures = failures;
this.mustSucceed = mustSucceed;
}
}
}

View File

@@ -25,7 +25,7 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.15:tor-0.3.5.15.jar:2ff5b5a3b5eaa97d699629ad24ba9584b3199d0ffdb1ea7d8a02de3016b80e7a',
'org.briarproject:tor:0.3.5.17:tor-0.3.5.17.jar:ce0e1f4d8f14878e61b23a35a452bc0f2a8e3117ced5a74773cd78475fa7af39',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" version="1.1" id="svg4"
sodipodi:docname="ic_mailbox.svg" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg">
<defs id="defs8" />
<sodipodi:namedview id="namedview6" pagecolor="#7d7d7d" bordercolor="#666666" borderopacity="1.0"
inkscape:pageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0"
showgrid="false" inkscape:zoom="9.0780032" inkscape:cx="22.086355" inkscape:cy="10.850404"
inkscape:window-width="1920" inkscape:window-height="928" inkscape:window-x="0"
inkscape:window-y="108" inkscape:window-maximized="1" inkscape:current-layer="svg4" />
<path
d="m 5.0781637,2.0000001 c -0.5245356,0 -1.0278314,0.2105185 -1.3987365,0.5847785 C 3.3085215,2.9590375 3.1,3.4663153 3.1,3.9956004 V 16.767666 c 0,0.529289 0.2085215,1.037692 0.5794272,1.411969 0.3709051,0.37423 0.8742009,0.583635 1.3987365,0.583635 H 15.850874 l 3.588966,3.026135 C 20.019297,22.277986 20.9,21.862034 20.9,21.100087 V 3.9956004 C 20.9,3.4663153 20.691473,2.9590375 20.32057,2.5847786 19.949681,2.2105186 19.44635,2.0000001 18.921831,2.0000001 Z M 6.2648311,5.19273 H 17.73517 v 5.65018 h -4.00732 v 1.954238 h 1.465946 c 0.26433,0 0.396594,0.32267 0.209762,0.511253 l -3.160194,3.188138 c -0.11588,0.116871 -0.303625,0.116871 -0.419503,0 L 8.6636588,13.308401 C 8.4767702,13.119818 8.6091068,12.797148 8.8734113,12.797148 H 10.339359 V 10.84291 H 6.2648311 Z"
fill="white" id="path2" style="fill:#ffffff;stroke:none;stroke-width:0.590769" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,105 @@
<svg width="250" height="199" viewBox="0 0 250 199" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M95.4363 100.233C95.4363 93.5233 100.876 88.084 107.585 88.084H130.365C137.074 88.084 142.514 93.5233 142.514 100.233V123.012C142.514 129.722 137.074 135.161 130.365 135.161H107.585C100.876 135.161 95.4363 129.722 95.4363 123.012V100.233Z"
fill="#15212D" />
<path
d="M102.336 10.2609C102.336 4.59395 106.93 0 112.597 0H131.836C137.503 0 142.097 4.59395 142.097 10.2609V29.5C142.097 35.1669 137.503 39.7609 131.836 39.7609H112.597C106.93 39.7609 102.336 35.1669 102.336 29.5V10.2609Z"
fill="#15212D" />
<path
d="M34.8184 148.09C34.8184 143.427 38.5986 139.646 43.2618 139.646H59.0933C63.7565 139.646 67.5368 143.427 67.5368 148.09V163.921C67.5368 168.585 63.7565 172.365 59.0933 172.365H43.2618C38.5986 172.365 34.8184 168.585 34.8184 163.921V148.09Z"
fill="#15212D" />
<path
d="M110.024 186.044C110.024 183.557 112.04 181.541 114.527 181.541H122.971C125.458 181.541 127.475 183.557 127.475 186.044V194.488C127.475 196.976 125.458 198.992 122.971 198.992H114.527C112.04 198.992 110.024 196.976 110.024 194.488V186.044Z"
fill="#15212D" />
<path
d="M173.713 141.6C173.713 134.89 179.152 129.451 185.862 129.451H208.641C215.351 129.451 220.79 134.89 220.79 141.6V164.38C220.79 171.089 215.351 176.528 208.641 176.528H185.862C179.152 176.528 173.713 171.089 173.713 164.38V141.6Z"
fill="#15212D" />
<path
d="M232.049 95.4156C232.049 92.9284 234.065 90.9121 236.553 90.9121H244.997C247.484 90.9121 249.5 92.9284 249.5 95.4156V103.86C249.5 106.347 247.484 108.363 244.997 108.363H236.553C234.065 108.363 232.049 106.347 232.049 103.86V95.4156Z"
fill="#15212D" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M120.481 84.2273V45.2676H122.481V84.2273H120.481Z" fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M117.685 176.101L117.685 139.646H119.685L119.685 176.101H117.685Z" fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M3.27959 108.445L4.0457 106.598C4.3381 106.719 4.66032 106.787 5.00345 106.787H7.11444V108.787H5.00345C4.39282 108.787 3.81058 108.665 3.27959 108.445ZM11.3364 108.787V106.787H13.4474C13.7905 106.787 14.1128 106.719 14.4052 106.598L15.1713 108.445C14.6403 108.665 14.058 108.787 13.4474 108.787H11.3364ZM17.9509 97.9504H15.9509V95.8394C15.9509 95.4963 15.8829 95.174 15.7617 94.8816L17.6091 94.1155C17.8293 94.6465 17.9509 95.2288 17.9509 95.8394V97.9504ZM7.11444 91.3359H5.00345C4.39282 91.3359 3.81058 91.4575 3.27959 91.6777L4.0457 93.5251C4.3381 93.4039 4.66032 93.3359 5.00345 93.3359H7.11444V91.3359ZM0.500008 102.172H2.50001V104.283C2.50001 104.626 2.56793 104.949 2.68918 105.241L0.841732 106.007C0.621538 105.476 0.500008 104.894 0.500008 104.283V102.172ZM0.500008 97.9504H2.50001V95.8394C2.50001 95.4963 2.56793 95.174 2.68918 94.8816L0.841731 94.1155C0.621538 94.6465 0.500008 95.2288 0.500008 95.8394V97.9504ZM11.3364 91.3359V93.3359H13.4474C13.7905 93.3359 14.1128 93.4039 14.4052 93.5251L15.1713 91.6777C14.6403 91.4575 14.058 91.3359 13.4474 91.3359H11.3364ZM17.9509 102.172H15.9509V104.283C15.9509 104.626 15.8829 104.949 15.7617 105.241L17.6091 106.007C17.8293 105.476 17.9509 104.894 17.9509 104.283V102.172Z"
fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M41.1771 64.5169V62.6484H43.1771V64.5169H41.1771ZM41.1771 71.9908V68.2538H43.1771V71.9908H41.1771ZM41.1771 79.4647V75.7277H43.1771V79.4647H41.1771ZM41.1771 86.9385V83.2016H43.1771V86.9385H41.1771ZM41.1771 94.4124V90.6755H43.1771V94.4124H41.1771ZM41.1771 99.0178V98.1493H43.1771V99.0178H44.9095V101.018H39.681V99.0178H41.1771ZM24.7048 101.018H22.2087V99.0178H24.7048V101.018ZM34.6889 101.018H29.6969V99.0178H34.6889V101.018ZM52.1068 101.018H50.3744V99.0178H54.8543V101.018H54.1068V102.148H52.1068V101.018ZM61.8442 101.018H58.3493V99.0178H61.8442V101.018ZM67.0867 101.018H65.3392V99.0178H67.0867C68.0476 99.0178 68.9663 99.2092 69.8048 99.557L69.0387 101.404C68.4388 101.156 67.7801 101.018 67.0867 101.018ZM72.187 106.118C72.187 105.425 72.0492 104.766 71.8004 104.166L73.6478 103.4C73.9955 104.239 74.187 105.157 74.187 106.118C74.187 106.812 74.3248 107.47 74.5736 108.07L72.7261 108.836C72.3784 107.998 72.187 107.079 72.187 106.118ZM52.1068 110.668V106.408H54.1068V110.668H52.1068ZM79.2873 113.218C78.3264 113.218 77.4077 113.027 76.5691 112.679L77.3352 110.832C77.9352 111.081 78.5939 111.218 79.2873 111.218H81.5905V113.218H79.2873ZM88.5 113.218H86.1968V111.218H88.5V113.218ZM52.1068 119.188V114.928H54.1068V119.188H52.1068ZM52.1068 127.709V123.449H54.1068V127.709H52.1068ZM52.1068 134.099V131.969H54.1068V134.099H52.1068Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M105.214 134.93L105.602 132.968C106.242 133.095 106.905 133.161 107.585 133.161H109.484V135.161H107.585C106.774 135.161 105.981 135.082 105.214 134.93ZM128.466 135.161V133.161H130.365C131.045 133.161 131.708 133.095 132.347 132.968L132.736 134.93C131.969 135.082 131.176 135.161 130.365 135.161H128.466ZM137.115 133.115L136.002 131.453C137.11 130.712 138.064 129.757 138.805 128.65L140.467 129.762C139.58 131.087 138.44 132.228 137.115 133.115ZM142.514 102.131H140.514V100.233C140.514 99.5526 140.447 98.8898 140.32 98.2501L142.282 97.8619C142.434 98.6288 142.514 99.4216 142.514 100.233V102.131ZM140.467 93.4828L138.805 94.5954C138.064 93.4878 137.11 92.5338 136.002 91.7922L137.115 90.1303C138.44 91.0173 139.58 92.1579 140.467 93.4828ZM109.484 88.084H107.585C106.774 88.084 105.981 88.1635 105.214 88.3152L105.602 90.2772C106.242 90.1507 106.905 90.084 107.585 90.084H109.484V88.084ZM100.835 90.1303L101.948 91.7922C100.84 92.5338 99.8861 93.4878 99.1445 94.5954L97.4826 93.4828C98.3696 92.1579 99.5102 91.0173 100.835 90.1303ZM95.4363 121.114H97.4363V123.012C97.4363 123.693 97.503 124.355 97.6295 124.995L95.6675 125.383C95.5158 124.616 95.4363 123.824 95.4363 123.012V121.114ZM97.4826 129.762L99.1445 128.65C99.8861 129.757 100.84 130.712 101.948 131.453L100.835 133.115C99.5102 132.228 98.3696 131.087 97.4826 129.762ZM95.4363 117.317H97.4363V113.521H95.4363V117.317ZM95.4363 109.724H97.4363V105.928H95.4363V109.724ZM95.4363 102.131H97.4363V100.233C97.4363 99.5526 97.503 98.8898 97.6295 98.2501L95.6675 97.8619C95.5158 98.6288 95.4363 99.4216 95.4363 100.233V102.131ZM113.28 88.084V90.084H117.077V88.084H113.28ZM120.873 88.084V90.084H124.67V88.084H120.873ZM128.466 88.084V90.084H130.365C131.045 90.084 131.708 90.1507 132.347 90.2772L132.736 88.3152C131.969 88.1635 131.176 88.084 130.365 88.084H128.466ZM142.514 105.928H140.514V109.724H142.514V105.928ZM142.514 113.521H140.514V117.317H142.514V113.521ZM142.514 121.114H140.514V123.012C140.514 123.693 140.447 124.355 140.32 124.995L142.282 125.383C142.434 124.616 142.514 123.824 142.514 123.012V121.114ZM124.67 135.161V133.161H120.873V135.161H124.67ZM117.077 135.161V133.161H113.28V135.161H117.077Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M188.618 86.5389V84.6738H190.618V86.5389H188.618ZM188.618 93.9991V90.269H190.618V93.9991H188.618ZM188.618 97.7293H190.618V98.5943H191.517V100.594H190.618V101.183H188.618V97.7293ZM199.112 100.594H195.315V98.5943H199.112V100.594ZM206.707 100.594H202.91V98.5943H206.707V100.594ZM214.303 100.594H210.505V98.5943H214.303V100.594ZM221.898 100.594H218.1V98.5943H221.898V100.594ZM227.595 100.594H225.696V98.5943H227.595V100.594ZM188.618 107.537V104.36H190.618V107.537H188.618ZM188.618 111.303V110.715H190.618V111.359C190.865 111.387 191.109 111.425 191.35 111.472L190.962 113.434C190.528 113.349 190.079 113.303 189.618 113.303H187.737V111.303H188.618ZM153.881 113.303H152V111.303H153.881V113.303ZM161.405 113.303H157.643V111.303H161.405V113.303ZM168.928 113.303H165.166V111.303H168.928V113.303ZM176.452 113.303H172.69V111.303H176.452V113.303ZM183.975 113.303H180.214V111.303H183.975V113.303ZM195.34 116.361C194.837 115.611 194.19 114.964 193.44 114.462L194.552 112.8C195.52 113.448 196.353 114.281 197.001 115.249L195.34 116.361ZM196.498 120.183C196.498 119.722 196.453 119.273 196.367 118.839L198.329 118.451C198.44 119.012 198.498 119.591 198.498 120.183V121.203H196.498V120.183ZM196.498 124.261V123.242H198.498V124.261H196.498Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M183.491 176.297L183.879 174.335C184.519 174.462 185.182 174.528 185.862 174.528H187.76V176.528H185.862C185.051 176.528 184.258 176.449 183.491 176.297ZM206.743 176.528V174.528H208.641C209.322 174.528 209.985 174.462 210.624 174.335L211.012 176.297C210.246 176.449 209.453 176.528 208.641 176.528H206.743ZM215.392 174.482L214.279 172.82C215.387 172.079 216.341 171.125 217.082 170.017L218.744 171.13C217.857 172.455 216.717 173.595 215.392 174.482ZM220.79 143.498H218.79V141.6C218.79 140.92 218.724 140.257 218.597 139.617L220.559 139.229C220.711 139.996 220.79 140.789 220.79 141.6V143.498ZM218.744 134.85L217.082 135.963C216.341 134.855 215.387 133.901 214.279 133.159L215.392 131.497C216.717 132.384 217.857 133.525 218.744 134.85ZM187.76 129.451H185.862C185.051 129.451 184.258 129.531 183.491 129.682L183.879 131.644C184.519 131.518 185.182 131.451 185.862 131.451H187.76V129.451ZM179.112 131.497L180.225 133.159C179.117 133.901 178.163 134.855 177.421 135.963L175.759 134.85C176.646 133.525 177.787 132.384 179.112 131.497ZM173.713 162.481H175.713V164.38C175.713 165.06 175.78 165.723 175.906 166.362L173.944 166.751C173.793 165.984 173.713 165.191 173.713 164.38V162.481ZM175.759 171.13L177.421 170.017C178.163 171.125 179.117 172.079 180.225 172.82L179.112 174.482C177.787 173.595 176.646 172.455 175.759 171.13ZM173.713 158.685H175.713V154.888H173.713V158.685ZM173.713 151.092H175.713V147.295H173.713V151.092ZM173.713 143.498H175.713V141.6C175.713 140.92 175.78 140.257 175.906 139.617L173.944 139.229C173.793 139.996 173.713 140.789 173.713 141.6V143.498ZM191.557 129.451V131.451H195.354V129.451H191.557ZM199.15 129.451V131.451H202.947V129.451H199.15ZM206.743 129.451V131.451H208.641C209.322 131.451 209.985 131.518 210.624 131.644L211.012 129.682C210.246 129.531 209.453 129.451 208.641 129.451H206.743ZM220.79 147.295H218.79V151.092H220.79V147.295ZM220.79 154.888H218.79V158.685H220.79V154.888ZM220.79 162.481H218.79V164.38C218.79 165.06 218.724 165.723 218.597 166.362L220.559 166.751C220.711 165.984 220.79 165.191 220.79 164.38V162.481ZM202.947 176.528V174.528H199.15V176.528H202.947ZM195.354 176.528V174.528H191.557V176.528H195.354Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M110.594 39.5656L110.982 37.6036C111.503 37.7066 112.042 37.7609 112.597 37.7609H115.001V39.7609H112.597C111.911 39.7609 111.242 39.6937 110.594 39.5656ZM129.431 39.7609V37.7609H131.836C132.39 37.7609 132.93 37.7066 133.45 37.6036L133.838 39.5656C133.191 39.6937 132.521 39.7609 131.836 39.7609H129.431ZM137.537 38.0326L136.424 36.3706C137.326 35.767 138.103 34.9902 138.706 34.0885L140.368 35.2011C139.619 36.3201 138.656 37.2835 137.537 38.0326ZM142.097 12.6658H140.097V10.2609C140.097 9.7066 140.042 9.167 139.939 8.64647L141.901 8.25834C142.029 8.90601 142.097 9.57559 142.097 10.2609V12.6658ZM140.368 4.55977L138.706 5.67241C138.103 4.77069 137.326 3.99391 136.424 3.39022L137.537 1.72828C138.656 2.47741 139.619 3.44081 140.368 4.55977ZM115.001 0H112.597C111.911 0 111.242 0.0671774 110.594 0.195306L110.982 2.15728C111.503 2.05431 112.042 2 112.597 2H115.001V0ZM106.895 1.72829L108.008 3.39022C107.106 3.99391 106.33 4.77069 105.726 5.67241L104.064 4.55977C104.813 3.44081 105.777 2.47741 106.895 1.72829ZM102.336 27.0951H104.336V29.5C104.336 30.0543 104.39 30.5939 104.493 31.1144L102.531 31.5025C102.403 30.8549 102.336 30.1853 102.336 29.5V27.0951ZM104.064 35.2011L105.726 34.0885C106.33 34.9902 107.106 35.767 108.008 36.3706L106.895 38.0326C105.777 37.2835 104.813 36.3201 104.064 35.2011ZM102.336 22.2853H104.336V17.4755H102.336V22.2853ZM102.336 12.6658H104.336V10.2609C104.336 9.7066 104.39 9.167 104.493 8.64647L102.531 8.25834C102.403 8.90601 102.336 9.57559 102.336 10.2609V12.6658ZM119.811 0V2H124.621V0H119.811ZM129.431 0V2H131.836C132.39 2 132.93 2.05431 133.45 2.15728L133.838 0.195306C133.191 0.0671773 132.521 0 131.836 0H129.431ZM142.097 17.4755H140.097V22.2853H142.097V17.4755ZM142.097 27.0951H140.097V29.5C140.097 30.0543 140.042 30.5939 139.939 31.1144L141.901 31.5025C142.029 30.8549 142.097 30.1853 142.097 29.5V27.0951ZM124.621 39.7609V37.7609H119.811V39.7609H124.621Z"
fill="#435B77" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M40.0298 171.724L40.7959 169.877C41.5528 170.191 42.3845 170.365 43.2618 170.365H45.2408V172.365H43.2618C42.117 172.365 41.0253 172.137 40.0298 171.724ZM57.1144 172.365V170.365H59.0933C59.9707 170.365 60.8023 170.191 61.5593 169.877L62.3254 171.724C61.3298 172.137 60.2382 172.365 59.0933 172.365H57.1144ZM67.5368 150.069H65.5368V148.09C65.5368 147.213 65.3625 146.381 65.0486 145.624L66.8961 144.858C67.3089 145.853 67.5368 146.945 67.5368 148.09V150.069ZM45.2408 139.646H43.2618C42.117 139.646 41.0253 139.874 40.0298 140.287L40.7959 142.135C41.5528 141.821 42.3845 141.646 43.2618 141.646H45.2408V139.646ZM34.8184 161.943H36.8184V163.921C36.8184 164.799 36.9926 165.63 37.3065 166.387L35.4591 167.153C35.0462 166.158 34.8184 165.066 34.8184 163.921V161.943ZM34.8184 157.985H36.8184V154.027H34.8184V157.985ZM34.8184 150.069H36.8184V148.09C36.8184 147.213 36.9926 146.381 37.3065 145.624L35.4591 144.858C35.0462 145.853 34.8184 146.945 34.8184 148.09V150.069ZM49.1986 139.646V141.646H53.1565V139.646H49.1986ZM57.1144 139.646V141.646H59.0933C59.9707 141.646 60.8023 141.821 61.5593 142.135L62.3254 140.287C61.3298 139.874 60.2382 139.646 59.0933 139.646H57.1144ZM67.5368 154.027H65.5368V157.985H67.5368V154.027ZM67.5368 161.943H65.5368V163.921C65.5368 164.799 65.3625 165.63 65.0486 166.387L66.8961 167.154C67.3089 166.158 67.5368 165.066 67.5368 163.921V161.943ZM53.1565 172.365V170.365H49.1986V172.365H53.1565Z"
fill="#435B77" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M112.803 198.65L113.569 196.803C113.862 196.924 114.184 196.992 114.527 196.992H116.638V198.992H114.527C113.917 198.992 113.334 198.87 112.803 198.65ZM120.86 198.992V196.992H122.971C123.314 196.992 123.636 196.924 123.929 196.803L124.695 198.65C124.164 198.87 123.582 198.992 122.971 198.992H120.86ZM127.475 188.155H125.475V186.044C125.475 185.701 125.407 185.379 125.285 185.087L127.133 184.321C127.353 184.852 127.475 185.434 127.475 186.044V188.155ZM116.638 181.541H114.527C113.917 181.541 113.334 181.663 112.803 181.883L113.569 183.73C113.862 183.609 114.184 183.541 114.527 183.541H116.638V181.541ZM110.024 192.377H112.024V194.488C112.024 194.832 112.092 195.154 112.213 195.446L110.365 196.212C110.145 195.681 110.024 195.099 110.024 194.488V192.377ZM110.024 188.155H112.024V186.044C112.024 185.701 112.092 185.379 112.213 185.087L110.365 184.321C110.145 184.852 110.024 185.434 110.024 186.044V188.155ZM120.86 181.541V183.541H122.971C123.314 183.541 123.636 183.609 123.929 183.73L124.695 181.883C124.164 181.663 123.582 181.541 122.971 181.541H120.86ZM127.475 192.377H125.475V194.488C125.475 194.832 125.407 195.154 125.285 195.446L127.133 196.212C127.353 195.681 127.475 195.099 127.475 194.488V192.377Z"
fill="#435B77" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M178.996 79.8234L179.762 77.976C180.48 78.2737 181.269 78.4391 182.101 78.4391H184.003V80.4391H182.101C181.001 80.4391 179.952 80.2201 178.996 79.8234ZM195.412 80.4391V78.4391H197.314C198.146 78.4391 198.935 78.2737 199.653 77.976L200.419 79.8234C199.463 80.2201 198.414 80.4391 197.314 80.4391H195.412ZM205.427 59.0149H203.427V57.1133C203.427 56.2807 203.262 55.4918 202.964 54.7737L204.811 54.0076C205.208 54.9643 205.427 56.0132 205.427 57.1133V59.0149ZM184.003 49H182.101C181.001 49 179.952 49.2189 178.996 49.6156L179.762 51.4631C180.48 51.1653 181.269 51 182.101 51H184.003V49ZM173.988 70.4242H175.988V72.3258C175.988 73.1584 176.153 73.9473 176.451 74.6653L174.604 75.4315C174.207 74.4748 173.988 73.4259 173.988 72.3258V70.4242ZM173.988 66.6211H175.988V62.818H173.988V66.6211ZM173.988 59.0149H175.988V57.1133C175.988 56.2807 176.153 55.4918 176.451 54.7737L174.604 54.0076C174.207 54.9643 173.988 56.0132 173.988 57.1133V59.0149ZM187.806 49V51H191.609V49H187.806ZM195.412 49V51H197.314C198.146 51 198.935 51.1653 199.653 51.4631L200.419 49.6156C199.463 49.2189 198.414 49 197.314 49H195.412ZM205.427 62.818H203.427V66.6211H205.427V62.818ZM205.427 70.4242H203.427V72.3258C203.427 73.1584 203.262 73.9473 202.964 74.6653L204.811 75.4315C205.208 74.4748 205.427 73.4259 205.427 72.3258V70.4242ZM191.609 80.4391V78.4391H187.806V80.4391H191.609Z"
fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M234.829 108.021L235.595 106.174C235.887 106.295 236.209 106.363 236.553 106.363H238.664V108.363H236.553C235.942 108.363 235.36 108.241 234.829 108.021ZM242.886 108.363V106.363H244.997C245.34 106.363 245.662 106.295 245.954 106.174L246.72 108.021C246.189 108.241 245.607 108.363 244.997 108.363H242.886ZM249.5 97.5265H247.5V95.4156C247.5 95.0724 247.432 94.7502 247.311 94.4578L249.158 93.6917C249.378 94.2227 249.5 94.8049 249.5 95.4156V97.5265ZM238.664 90.9121H236.553C235.942 90.9121 235.36 91.0336 234.829 91.2538L235.595 93.1013C235.887 92.98 236.209 92.9121 236.553 92.9121H238.664V90.9121ZM232.049 101.749H234.049V103.86C234.049 104.203 234.117 104.525 234.238 104.817L232.391 105.583C232.171 105.052 232.049 104.47 232.049 103.86V101.749ZM232.049 97.5265H234.049V95.4156C234.049 95.0724 234.117 94.7502 234.238 94.4578L232.391 93.6917C232.171 94.2227 232.049 94.8049 232.049 95.4156V97.5265ZM242.886 90.9121V92.9121H244.997C245.34 92.9121 245.662 92.98 245.954 93.1013L246.72 91.2538C246.189 91.0336 245.607 90.9121 244.997 90.9121H242.886ZM249.5 101.749H247.5V103.86C247.5 104.203 247.432 104.525 247.311 104.817L249.158 105.583C249.378 105.052 249.5 104.47 249.5 103.86V101.749Z"
fill="#435B77" />
<path
d="M29.8254 34.377V53.6604C29.8254 55.2256 31.1003 56.5166 32.6817 56.5166H34.021C35.5863 56.5166 36.8772 55.2256 36.8772 53.6604V34.377H29.8254Z"
fill="#82C91E" />
<path
d="M36.8611 25.0827V21.4519C36.8611 19.8866 35.5863 18.5957 34.021 18.5957H32.6817C31.1164 18.5957 29.8254 19.8866 29.8254 21.4519V25.0827H36.8611Z"
fill="#82C91E" />
<path
d="M52.5136 40.7353V21.4519C52.5136 19.8866 51.2388 18.5957 49.6735 18.5957H48.3342C46.7689 18.5957 45.478 19.8866 45.478 21.4519V40.7353H52.5136Z"
fill="#82C91E" />
<path
d="M45.478 50.0293V53.6601C45.478 55.2253 46.7689 56.5163 48.3342 56.5163H49.6735C51.2388 56.5163 52.5298 55.2253 52.5298 53.6601V50.0293H45.478Z"
fill="#82C91E" />
<path
d="M28.6957 41.8652H25.065C23.4997 41.8652 22.2087 43.14 22.2087 44.7214V46.0608C22.2087 47.6261 23.4836 48.917 25.065 48.917H28.6957V41.8652Z"
fill="#95DE2D" />
<path
d="M57.2738 41.8652H37.9903V48.917H57.2738C58.839 48.917 60.13 47.6261 60.13 46.0608V44.7214C60.13 43.14 58.839 41.8652 57.2738 41.8652Z"
fill="#95DE2D" />
<path
d="M44.3484 26.2129H25.065C23.4997 26.2129 22.2087 27.4877 22.2087 29.0691V30.4084C22.2087 31.9737 23.4836 33.2647 25.065 33.2647H44.3484V26.2129Z"
fill="#95DE2D" />
<path
d="M57.2738 26.2128H53.6431V33.2646H57.2738C58.8391 33.2646 60.1301 31.9898 60.1301 30.4084V29.069C60.1301 27.4876 58.8391 26.2128 57.2738 26.2128Z"
fill="#95DE2D" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M108.864 99.4959H129.086C129.852 99.4959 130.587 99.8008 131.129 100.343C131.671 100.886 131.975 101.622 131.975 102.39L131.975 127.191C131.975 128.296 130.688 128.899 129.841 128.191L124.6 123.804H108.864C108.098 123.804 107.363 123.499 106.821 122.956C106.279 122.413 105.975 121.677 105.975 120.91V102.39C105.975 101.622 106.279 100.886 106.821 100.343C107.363 99.8008 108.098 99.4959 108.864 99.4959ZM116.549 112.318H110.597V104.126H127.353V112.318H121.499V115.152H123.64C124.026 115.152 124.22 115.619 123.947 115.893L119.331 120.517C119.161 120.686 118.887 120.686 118.718 120.517L114.102 115.893C113.829 115.619 114.022 115.152 114.408 115.152H116.549V112.318Z"
fill="#95DE2D" />
<path
d="M190.408 151.152V162.299C190.408 163.204 191.145 163.95 192.059 163.95H192.833C193.738 163.95 194.484 163.204 194.484 162.299V151.152H190.408Z"
fill="#82C91E" />
<path
d="M194.475 145.779V143.68C194.475 142.775 193.738 142.029 192.833 142.029H192.059C191.154 142.029 190.408 142.775 190.408 143.68V145.779H194.475Z"
fill="#82C91E" />
<path
d="M203.523 154.827V143.68C203.523 142.775 202.786 142.029 201.881 142.029H201.107C200.202 142.029 199.456 142.775 199.456 143.68V154.827H203.523Z"
fill="#82C91E" />
<path
d="M199.456 160.201V162.3C199.456 163.204 200.202 163.951 201.107 163.951H201.881C202.786 163.951 203.533 163.204 203.533 162.3V160.201H199.456Z"
fill="#82C91E" />
<path
d="M189.755 155.48H187.656C186.751 155.48 186.005 156.217 186.005 157.131V157.905C186.005 158.81 186.742 159.556 187.656 159.556H189.755V155.48Z"
fill="#95DE2D" />
<path
d="M206.275 155.48H195.128V159.556H206.275C207.18 159.556 207.926 158.81 207.926 157.905V157.131C207.926 156.217 207.18 155.48 206.275 155.48Z"
fill="#95DE2D" />
<path
d="M198.803 146.433H187.656C186.751 146.433 186.005 147.17 186.005 148.084V148.859C186.005 149.763 186.742 150.51 187.656 150.51H198.803V146.433Z"
fill="#95DE2D" />
<path
d="M206.275 146.433H204.176V150.51H206.275C207.18 150.51 207.926 149.773 207.926 148.859V148.084C207.926 147.17 207.18 146.433 206.275 146.433Z"
fill="#95DE2D" />
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,105 @@
<svg width="250" height="200" viewBox="0 0 250 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M95.4363 100.737C95.4363 94.0273 100.876 88.588 107.585 88.588H130.365C137.074 88.588 142.514 94.0273 142.514 100.737V123.516C142.514 130.226 137.074 135.665 130.365 135.665H107.585C100.876 135.665 95.4363 130.226 95.4363 123.516V100.737Z"
fill="#F6FAFE" />
<path
d="M102.336 10.7649C102.336 5.09801 106.93 0.504059 112.597 0.504059H131.836C137.503 0.504059 142.097 5.09801 142.097 10.7649V30.0041C142.097 35.671 137.503 40.2649 131.836 40.2649H112.597C106.93 40.2649 102.336 35.671 102.336 30.0041V10.7649Z"
fill="#F6FAFE" />
<path
d="M34.8184 148.594C34.8184 143.931 38.5986 140.151 43.2618 140.151H59.0933C63.7565 140.151 67.5368 143.931 67.5368 148.594V164.426C67.5368 169.089 63.7565 172.869 59.0933 172.869H43.2618C38.5986 172.869 34.8184 169.089 34.8184 164.426V148.594Z"
fill="#F6FAFE" />
<path
d="M110.024 186.549C110.024 184.061 112.04 182.045 114.527 182.045H122.971C125.458 182.045 127.475 184.061 127.475 186.549V194.992C127.475 197.48 125.458 199.496 122.971 199.496H114.527C112.04 199.496 110.024 197.48 110.024 194.992V186.549Z"
fill="#F6FAFE" />
<path
d="M173.713 142.104C173.713 135.395 179.152 129.955 185.862 129.955H208.641C215.351 129.955 220.79 135.395 220.79 142.104V164.884C220.79 171.593 215.351 177.033 208.641 177.033H185.862C179.152 177.033 173.713 171.593 173.713 164.884V142.104Z"
fill="#F6FAFE" />
<path
d="M232.049 95.9196C232.049 93.4324 234.065 91.4162 236.553 91.4162H244.997C247.484 91.4162 249.5 93.4324 249.5 95.9196V104.364C249.5 106.851 247.484 108.867 244.997 108.867H236.553C234.065 108.867 232.049 106.851 232.049 104.364V95.9196Z"
fill="#F6FAFE" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M120.481 84.7314V45.7716H122.481V84.7314H120.481Z" fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M117.685 176.605L117.685 140.151H119.685L119.685 176.605H117.685Z" fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M3.27959 108.949L4.0457 107.102C4.3381 107.223 4.66032 107.291 5.00345 107.291H7.11444V109.291H5.00345C4.39282 109.291 3.81058 109.169 3.27959 108.949ZM11.3364 109.291V107.291H13.4474C13.7905 107.291 14.1128 107.223 14.4052 107.102L15.1713 108.949C14.6403 109.169 14.058 109.291 13.4474 109.291H11.3364ZM17.9509 98.4544H15.9509V96.3434C15.9509 96.0003 15.8829 95.6781 15.7617 95.3857L17.6091 94.6196C17.8293 95.1506 17.9509 95.7328 17.9509 96.3434V98.4544ZM7.11444 91.84H5.00345C4.39282 91.84 3.81058 91.9615 3.27959 92.1817L4.0457 94.0292C4.3381 93.9079 4.66032 93.84 5.00345 93.84H7.11444V91.84ZM0.500008 102.676H2.50001V104.787C2.50001 105.131 2.56793 105.453 2.68918 105.745L0.841732 106.511C0.621538 105.98 0.500008 105.398 0.500008 104.787V102.676ZM0.500008 98.4544H2.50001V96.3434C2.50001 96.0003 2.56793 95.6781 2.68918 95.3857L0.841731 94.6196C0.621538 95.1506 0.500008 95.7328 0.500008 96.3434V98.4544ZM11.3364 91.84V93.84H13.4474C13.7905 93.84 14.1128 93.9079 14.4052 94.0292L15.1713 92.1817C14.6403 91.9615 14.058 91.84 13.4474 91.84H11.3364ZM17.9509 102.676H15.9509V104.787C15.9509 105.131 15.8829 105.453 15.7617 105.745L17.6091 106.511C17.8293 105.98 17.9509 105.398 17.9509 104.787V102.676Z"
fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M41.1771 65.021V63.1525H43.1771V65.021H41.1771ZM41.1771 72.4948V68.7579H43.1771V72.4948H41.1771ZM41.1771 79.9687V76.2318H43.1771V79.9687H41.1771ZM41.1771 87.4426V83.7057H43.1771V87.4426H41.1771ZM41.1771 94.9165V91.1795H43.1771V94.9165H41.1771ZM41.1771 99.5219V98.6534H43.1771V99.5219H44.9095V101.522H39.681V99.5219H41.1771ZM24.7048 101.522H22.2087V99.5219H24.7048V101.522ZM34.6889 101.522H29.6969V99.5219H34.6889V101.522ZM52.1068 101.522H50.3744V99.5219H54.8543V101.522H54.1068V102.652H52.1068V101.522ZM61.8442 101.522H58.3493V99.5219H61.8442V101.522ZM67.0867 101.522H65.3392V99.5219H67.0867C68.0476 99.5219 68.9663 99.7133 69.8048 100.061L69.0387 101.908C68.4388 101.66 67.7801 101.522 67.0867 101.522ZM72.187 106.622C72.187 105.929 72.0492 105.27 71.8004 104.67L73.6478 103.904C73.9955 104.743 74.187 105.661 74.187 106.622C74.187 107.316 74.3248 107.974 74.5736 108.574L72.7261 109.34C72.3784 108.502 72.187 107.583 72.187 106.622ZM52.1068 111.172V106.912H54.1068V111.172H52.1068ZM79.2873 113.722C78.3264 113.722 77.4077 113.531 76.5691 113.183L77.3352 111.336C77.9352 111.585 78.5939 111.722 79.2873 111.722H81.5905V113.722H79.2873ZM88.5 113.722H86.1968V111.722H88.5V113.722ZM52.1068 119.692V115.432H54.1068V119.692H52.1068ZM52.1068 128.213V123.953H54.1068V128.213H52.1068ZM52.1068 134.603V132.473H54.1068V134.603H52.1068Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M105.214 135.434L105.602 133.472C106.242 133.599 106.905 133.665 107.585 133.665H109.484V135.665H107.585C106.774 135.665 105.981 135.586 105.214 135.434ZM128.466 135.665V133.665H130.365C131.045 133.665 131.708 133.599 132.347 133.472L132.736 135.434C131.969 135.586 131.176 135.665 130.365 135.665H128.466ZM137.115 133.619L136.002 131.957C137.11 131.216 138.064 130.262 138.805 129.154L140.467 130.267C139.58 131.591 138.44 132.732 137.115 133.619ZM142.514 102.635H140.514V100.737C140.514 100.057 140.447 99.3939 140.32 98.7541L142.282 98.366C142.434 99.1329 142.514 99.9257 142.514 100.737V102.635ZM140.467 93.9869L138.805 95.0995C138.064 93.9919 137.11 93.0378 136.002 92.2963L137.115 90.6344C138.44 91.5213 139.58 92.662 140.467 93.9869ZM109.484 88.588H107.585C106.774 88.588 105.981 88.6676 105.214 88.8193L105.602 90.7813C106.242 90.6547 106.905 90.588 107.585 90.588H109.484V88.588ZM100.835 90.6344L101.948 92.2963C100.84 93.0378 99.8861 93.9919 99.1445 95.0995L97.4826 93.9869C98.3696 92.662 99.5102 91.5213 100.835 90.6344ZM95.4363 121.618H97.4363V123.516C97.4363 124.197 97.503 124.86 97.6295 125.499L95.6675 125.887C95.5158 125.121 95.4363 124.328 95.4363 123.516V121.618ZM97.4826 130.267L99.1445 129.154C99.8861 130.262 100.84 131.216 101.948 131.957L100.835 133.619C99.5102 132.732 98.3696 131.591 97.4826 130.267ZM95.4363 117.822H97.4363V114.025H95.4363V117.822ZM95.4363 110.228H97.4363V106.432H95.4363V110.228ZM95.4363 102.635H97.4363V100.737C97.4363 100.057 97.503 99.3939 97.6295 98.7541L95.6675 98.366C95.5158 99.1329 95.4363 99.9257 95.4363 100.737V102.635ZM113.28 88.588V90.588H117.077V88.588H113.28ZM120.873 88.588V90.588H124.67V88.588H120.873ZM128.466 88.588V90.588H130.365C131.045 90.588 131.708 90.6547 132.347 90.7813L132.736 88.8193C131.969 88.6676 131.176 88.588 130.365 88.588H128.466ZM142.514 106.432H140.514V110.228H142.514V106.432ZM142.514 114.025H140.514V117.822H142.514V114.025ZM142.514 121.618H140.514V123.516C140.514 124.197 140.447 124.86 140.32 125.499L142.282 125.887C142.434 125.121 142.514 124.328 142.514 123.516V121.618ZM124.67 135.665V133.665H120.873V135.665H124.67ZM117.077 135.665V133.665H113.28V135.665H117.077Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M188.618 87.0429V85.1779H190.618V87.0429H188.618ZM188.618 94.5032V90.7731H190.618V94.5032H188.618ZM188.618 98.2333H190.618V99.0984H191.517V101.098H190.618V101.687H188.618V98.2333ZM199.112 101.098H195.315V99.0984H199.112V101.098ZM206.707 101.098H202.91V99.0984H206.707V101.098ZM214.303 101.098H210.505V99.0984H214.303V101.098ZM221.898 101.098H218.1V99.0984H221.898V101.098ZM227.595 101.098H225.696V99.0984H227.595V101.098ZM188.618 108.042V104.864H190.618V108.042H188.618ZM188.618 111.807V111.219H190.618V111.863C190.865 111.891 191.109 111.929 191.35 111.976L190.962 113.938C190.528 113.853 190.079 113.807 189.618 113.807H187.737V111.807H188.618ZM153.881 113.807H152V111.807H153.881V113.807ZM161.405 113.807H157.643V111.807H161.405V113.807ZM168.928 113.807H165.166V111.807H168.928V113.807ZM176.452 113.807H172.69V111.807H176.452V113.807ZM183.975 113.807H180.214V111.807H183.975V113.807ZM195.34 116.865C194.837 116.115 194.19 115.468 193.44 114.966L194.552 113.304C195.52 113.952 196.353 114.785 197.001 115.753L195.34 116.865ZM196.498 120.687C196.498 120.226 196.453 119.777 196.367 119.343L198.329 118.955C198.44 119.516 198.498 120.095 198.498 120.687V121.707H196.498V120.687ZM196.498 124.765V123.746H198.498V124.765H196.498Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M183.491 176.801L183.879 174.839C184.519 174.966 185.182 175.033 185.862 175.033H187.76V177.033H185.862C185.051 177.033 184.258 176.953 183.491 176.801ZM206.743 177.033V175.033H208.641C209.322 175.033 209.985 174.966 210.624 174.839L211.012 176.801C210.246 176.953 209.453 177.033 208.641 177.033H206.743ZM215.392 174.986L214.279 173.324C215.387 172.583 216.341 171.629 217.082 170.521L218.744 171.634C217.857 172.959 216.717 174.099 215.392 174.986ZM220.79 144.002H218.79V142.104C218.79 141.424 218.724 140.761 218.597 140.121L220.559 139.733C220.711 140.5 220.79 141.293 220.79 142.104V144.002ZM218.744 135.354L217.082 136.467C216.341 135.359 215.387 134.405 214.279 133.663L215.392 132.002C216.717 132.889 217.857 134.029 218.744 135.354ZM187.76 129.955H185.862C185.051 129.955 184.258 130.035 183.491 130.186L183.879 132.148C184.519 132.022 185.182 131.955 185.862 131.955H187.76V129.955ZM179.112 132.002L180.225 133.663C179.117 134.405 178.163 135.359 177.421 136.467L175.759 135.354C176.646 134.029 177.787 132.889 179.112 132.002ZM173.713 162.985H175.713V164.884C175.713 165.564 175.78 166.227 175.906 166.866L173.944 167.255C173.793 166.488 173.713 165.695 173.713 164.884V162.985ZM175.759 171.634L177.421 170.521C178.163 171.629 179.117 172.583 180.225 173.324L179.112 174.986C177.787 174.099 176.646 172.959 175.759 171.634ZM173.713 159.189H175.713V155.392H173.713V159.189ZM173.713 151.596H175.713V147.799H173.713V151.596ZM173.713 144.002H175.713V142.104C175.713 141.424 175.78 140.761 175.906 140.121L173.944 139.733C173.793 140.5 173.713 141.293 173.713 142.104V144.002ZM191.557 129.955V131.955H195.354V129.955H191.557ZM199.15 129.955V131.955H202.947V129.955H199.15ZM206.743 129.955V131.955H208.641C209.322 131.955 209.985 132.022 210.624 132.148L211.012 130.186C210.246 130.035 209.453 129.955 208.641 129.955H206.743ZM220.79 147.799H218.79V151.596H220.79V147.799ZM220.79 155.392H218.79V159.189H220.79V155.392ZM220.79 162.985H218.79V164.884C218.79 165.564 218.724 166.227 218.597 166.866L220.559 167.255C220.711 166.488 220.79 165.695 220.79 164.884V162.985ZM202.947 177.033V175.033H199.15V177.033H202.947ZM195.354 177.033V175.033H191.557V177.033H195.354Z"
fill="#74B816" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M110.594 40.0696L110.982 38.1076C111.503 38.2106 112.042 38.2649 112.597 38.2649H115.001V40.2649H112.597C111.911 40.2649 111.242 40.1978 110.594 40.0696ZM129.431 40.2649V38.2649H131.836C132.39 38.2649 132.93 38.2106 133.45 38.1076L133.838 40.0696C133.191 40.1978 132.521 40.2649 131.836 40.2649H129.431ZM137.537 38.5366L136.424 36.8747C137.326 36.271 138.103 35.4942 138.706 34.5925L140.368 35.7052C139.619 36.8241 138.656 37.7875 137.537 38.5366ZM142.097 13.1698H140.097V10.7649C140.097 10.2107 140.042 9.67106 139.939 9.15053L141.901 8.76239C142.029 9.41006 142.097 10.0797 142.097 10.7649V13.1698ZM140.368 5.06383L138.706 6.17647C138.103 5.27474 137.326 4.49797 136.424 3.89428L137.537 2.23234C138.656 2.98147 139.619 3.94486 140.368 5.06383ZM115.001 0.504059H112.597C111.911 0.504059 111.242 0.571236 110.594 0.699365L110.982 2.66134C111.503 2.55836 112.042 2.50406 112.597 2.50406H115.001V0.504059ZM106.895 2.23235L108.008 3.89428C107.106 4.49797 106.33 5.27475 105.726 6.17647L104.064 5.06383C104.813 3.94487 105.777 2.98147 106.895 2.23235ZM102.336 27.5992H104.336V30.0041C104.336 30.5583 104.39 31.0979 104.493 31.6185L102.531 32.0066C102.403 31.3589 102.336 30.6893 102.336 30.0041V27.5992ZM104.064 35.7052L105.726 34.5925C106.33 35.4942 107.106 36.271 108.008 36.8747L106.895 38.5366C105.777 37.7875 104.813 36.8241 104.064 35.7052ZM102.336 22.7894H104.336V17.9796H102.336V22.7894ZM102.336 13.1698H104.336V10.7649C104.336 10.2107 104.39 9.67106 104.493 9.15053L102.531 8.7624C102.403 9.41007 102.336 10.0797 102.336 10.7649V13.1698ZM119.811 0.504059V2.50406H124.621V0.504059H119.811ZM129.431 0.504059V2.50406H131.836C132.39 2.50406 132.93 2.55836 133.45 2.66134L133.838 0.699365C133.191 0.571236 132.521 0.504059 131.836 0.504059H129.431ZM142.097 17.9796H140.097V22.7894H142.097V17.9796ZM142.097 27.5992H140.097V30.0041C140.097 30.5583 140.042 31.0979 139.939 31.6185L141.901 32.0066C142.029 31.3589 142.097 30.6893 142.097 30.0041V27.5992ZM124.621 40.2649V38.2649H119.811V40.2649H124.621Z"
fill="#435B77" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M40.0298 172.228L40.7959 170.381C41.5528 170.695 42.3845 170.869 43.2618 170.869H45.2408V172.869H43.2618C42.117 172.869 41.0253 172.641 40.0298 172.228ZM57.1144 172.869V170.869H59.0933C59.9707 170.869 60.8023 170.695 61.5593 170.381L62.3254 172.228C61.3298 172.641 60.2382 172.869 59.0933 172.869H57.1144ZM67.5368 150.573H65.5368V148.594C65.5368 147.717 65.3625 146.885 65.0486 146.128L66.8961 145.362C67.3089 146.358 67.5368 147.449 67.5368 148.594V150.573ZM45.2408 140.151H43.2618C42.117 140.151 41.0253 140.378 40.0298 140.791L40.7959 142.639C41.5528 142.325 42.3845 142.151 43.2618 142.151H45.2408V140.151ZM34.8184 162.447H36.8184V164.426C36.8184 165.303 36.9926 166.134 37.3065 166.891L35.4591 167.658C35.0462 166.662 34.8184 165.57 34.8184 164.426V162.447ZM34.8184 158.489H36.8184V154.531H34.8184V158.489ZM34.8184 150.573H36.8184V148.594C36.8184 147.717 36.9926 146.885 37.3065 146.128L35.4591 145.362C35.0462 146.358 34.8184 147.449 34.8184 148.594V150.573ZM49.1986 140.151V142.151H53.1565V140.151H49.1986ZM57.1144 140.151V142.151H59.0933C59.9707 142.151 60.8023 142.325 61.5593 142.639L62.3254 140.791C61.3298 140.378 60.2382 140.151 59.0933 140.151H57.1144ZM67.5368 154.531H65.5368V158.489H67.5368V154.531ZM67.5368 162.447H65.5368V164.426C65.5368 165.303 65.3625 166.134 65.0486 166.891L66.8961 167.658C67.3089 166.662 67.5368 165.57 67.5368 164.426V162.447ZM53.1565 172.869V170.869H49.1986V172.869H53.1565Z"
fill="#435B77" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M112.803 199.154L113.569 197.307C113.862 197.428 114.184 197.496 114.527 197.496H116.638V199.496H114.527C113.917 199.496 113.334 199.374 112.803 199.154ZM120.86 199.496V197.496H122.971C123.314 197.496 123.636 197.428 123.929 197.307L124.695 199.154C124.164 199.374 123.582 199.496 122.971 199.496H120.86ZM127.475 188.66H125.475V186.549C125.475 186.205 125.407 185.883 125.285 185.591L127.133 184.825C127.353 185.356 127.475 185.938 127.475 186.549V188.66ZM116.638 182.045H114.527C113.917 182.045 113.334 182.167 112.803 182.387L113.569 184.234C113.862 184.113 114.184 184.045 114.527 184.045H116.638V182.045ZM110.024 192.881H112.024V194.992C112.024 195.336 112.092 195.658 112.213 195.95L110.365 196.716C110.145 196.185 110.024 195.603 110.024 194.992V192.881ZM110.024 188.66H112.024V186.549C112.024 186.205 112.092 185.883 112.213 185.591L110.365 184.825C110.145 185.356 110.024 185.938 110.024 186.549V188.66ZM120.86 182.045V184.045H122.971C123.314 184.045 123.636 184.113 123.929 184.234L124.695 182.387C124.164 182.167 123.582 182.045 122.971 182.045H120.86ZM127.475 192.881H125.475V194.992C125.475 195.336 125.407 195.658 125.285 195.95L127.133 196.716C127.353 196.185 127.475 195.603 127.475 194.992V192.881Z"
fill="#435B77" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M178.996 80.3275L179.762 78.4801C180.48 78.7778 181.269 78.9431 182.101 78.9431H184.003V80.9431H182.101C181.001 80.9431 179.952 80.7242 178.996 80.3275ZM195.412 80.9431V78.9431H197.314C198.146 78.9431 198.935 78.7778 199.653 78.4801L200.419 80.3275C199.463 80.7242 198.414 80.9431 197.314 80.9431H195.412ZM205.427 59.5189H203.427V57.6174C203.427 56.7848 203.262 55.9958 202.964 55.2778L204.811 54.5117C205.208 55.4683 205.427 56.5173 205.427 57.6174V59.5189ZM184.003 49.5041H182.101C181.001 49.5041 179.952 49.723 178.996 50.1197L179.762 51.9672C180.48 51.6694 181.269 51.5041 182.101 51.5041H184.003V49.5041ZM173.988 70.9283H175.988V72.8298C175.988 73.6624 176.153 74.4514 176.451 75.1694L174.604 75.9355C174.207 74.9789 173.988 73.9299 173.988 72.8298V70.9283ZM173.988 67.1252H175.988V63.322H173.988V67.1252ZM173.988 59.5189H175.988V57.6174C175.988 56.7848 176.153 55.9958 176.451 55.2778L174.604 54.5117C174.207 55.4683 173.988 56.5173 173.988 57.6174V59.5189ZM187.806 49.5041V51.5041H191.609V49.5041H187.806ZM195.412 49.5041V51.5041H197.314C198.146 51.5041 198.935 51.6694 199.653 51.9672L200.419 50.1197C199.463 49.723 198.414 49.5041 197.314 49.5041H195.412ZM205.427 63.322H203.427V67.1252H205.427V63.322ZM205.427 70.9283H203.427V72.8298C203.427 73.6624 203.262 74.4514 202.964 75.1694L204.811 75.9355C205.208 74.9789 205.427 73.9299 205.427 72.8298V70.9283ZM191.609 80.9431V78.9431H187.806V80.9431H191.609Z"
fill="#657D99" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M234.829 108.525L235.595 106.678C235.887 106.799 236.209 106.867 236.553 106.867H238.664V108.867H236.553C235.942 108.867 235.36 108.745 234.829 108.525ZM242.886 108.867V106.867H244.997C245.34 106.867 245.662 106.799 245.954 106.678L246.72 108.525C246.189 108.745 245.607 108.867 244.997 108.867H242.886ZM249.5 98.0306H247.5V95.9196C247.5 95.5765 247.432 95.2543 247.311 94.9619L249.158 94.1957C249.378 94.7267 249.5 95.309 249.5 95.9196V98.0306ZM238.664 91.4162H236.553C235.942 91.4162 235.36 91.5377 234.829 91.7579L235.595 93.6053C235.887 93.4841 236.209 93.4162 236.553 93.4162H238.664V91.4162ZM232.049 102.253H234.049V104.364C234.049 104.707 234.117 105.029 234.238 105.321L232.391 106.087C232.171 105.556 232.049 104.974 232.049 104.364V102.253ZM232.049 98.0306H234.049V95.9196C234.049 95.5765 234.117 95.2543 234.238 94.9619L232.391 94.1957C232.171 94.7267 232.049 95.309 232.049 95.9196V98.0306ZM242.886 91.4162V93.4162H244.997C245.34 93.4162 245.662 93.4841 245.954 93.6053L246.72 91.7579C246.189 91.5377 245.607 91.4162 244.997 91.4162H242.886ZM249.5 102.253H247.5V104.364C247.5 104.707 247.432 105.029 247.311 105.321L249.158 106.087C249.378 105.556 249.5 104.974 249.5 104.364V102.253Z"
fill="#435B77" />
<path
d="M29.8254 34.881V54.1644C29.8254 55.7297 31.1003 57.0206 32.6817 57.0206H34.021C35.5863 57.0206 36.8772 55.7297 36.8772 54.1644V34.881H29.8254Z"
fill="#82C91E" />
<path
d="M36.8611 25.5867V21.956C36.8611 20.3907 35.5863 19.0998 34.021 19.0998H32.6817C31.1164 19.0998 29.8254 20.3907 29.8254 21.956V25.5867H36.8611Z"
fill="#82C91E" />
<path
d="M52.5136 41.2394V21.956C52.5136 20.3907 51.2388 19.0998 49.6735 19.0998H48.3342C46.7689 19.0998 45.478 20.3907 45.478 21.956V41.2394H52.5136Z"
fill="#82C91E" />
<path
d="M45.478 50.5334V54.1641C45.478 55.7294 46.7689 57.0203 48.3342 57.0203H49.6735C51.2388 57.0203 52.5298 55.7294 52.5298 54.1641V50.5334H45.478Z"
fill="#82C91E" />
<path
d="M28.6957 42.3693H25.065C23.4997 42.3693 22.2087 43.6441 22.2087 45.2255V46.5648C22.2087 48.1301 23.4836 49.4211 25.065 49.4211H28.6957V42.3693Z"
fill="#95DE2D" />
<path
d="M57.2738 42.3693H37.9903V49.4211H57.2738C58.839 49.4211 60.13 48.1301 60.13 46.5648V45.2255C60.13 43.6441 58.839 42.3693 57.2738 42.3693Z"
fill="#95DE2D" />
<path
d="M44.3484 26.7169H25.065C23.4997 26.7169 22.2087 27.9918 22.2087 29.5732V30.9125C22.2087 32.4778 23.4836 33.7687 25.065 33.7687H44.3484V26.7169Z"
fill="#95DE2D" />
<path
d="M57.2738 26.7169H53.6431V33.7687H57.2738C58.8391 33.7687 60.1301 32.4938 60.1301 30.9124V29.5731C60.1301 27.9917 58.8391 26.7169 57.2738 26.7169Z"
fill="#95DE2D" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M108.864 100H129.086C129.852 100 130.587 100.305 131.129 100.848C131.671 101.39 131.975 102.126 131.975 102.894L131.975 127.695C131.975 128.8 130.688 129.403 129.841 128.695L124.6 124.308H108.864C108.098 124.308 107.363 124.003 106.821 123.46C106.279 122.917 105.975 122.181 105.975 121.414V102.894C105.975 102.126 106.279 101.39 106.821 100.848C107.363 100.305 108.098 100 108.864 100ZM116.549 112.823H110.597V104.63H127.353V112.823H121.499V115.656H123.64C124.026 115.656 124.22 116.123 123.947 116.397L119.331 121.021C119.161 121.19 118.887 121.19 118.718 121.021L114.102 116.397C113.829 116.123 114.022 115.656 114.408 115.656H116.549V112.823Z"
fill="#95DE2D" />
<path
d="M190.408 151.656V162.803C190.408 163.708 191.145 164.454 192.059 164.454H192.833C193.738 164.454 194.484 163.708 194.484 162.803V151.656H190.408Z"
fill="#82C91E" />
<path
d="M194.475 146.283V144.184C194.475 143.279 193.738 142.533 192.833 142.533H192.059C191.154 142.533 190.408 143.279 190.408 144.184V146.283H194.475Z"
fill="#82C91E" />
<path
d="M203.523 155.331V144.184C203.523 143.279 202.786 142.533 201.881 142.533H201.107C200.202 142.533 199.456 143.279 199.456 144.184V155.331H203.523Z"
fill="#82C91E" />
<path
d="M199.456 160.705V162.804C199.456 163.709 200.202 164.455 201.107 164.455H201.881C202.786 164.455 203.533 163.709 203.533 162.804V160.705H199.456Z"
fill="#82C91E" />
<path
d="M189.755 155.984H187.656C186.751 155.984 186.005 156.721 186.005 157.635V158.409C186.005 159.314 186.742 160.061 187.656 160.061H189.755V155.984Z"
fill="#95DE2D" />
<path
d="M206.275 155.984H195.128V160.061H206.275C207.18 160.061 207.926 159.314 207.926 158.409V157.635C207.926 156.721 207.18 155.984 206.275 155.984Z"
fill="#95DE2D" />
<path
d="M198.803 146.937H187.656C186.751 146.937 186.005 147.674 186.005 148.588V149.363C186.005 150.267 186.742 151.014 187.656 151.014H198.803V146.937Z"
fill="#95DE2D" />
<path
d="M206.275 146.937H204.176V151.014H206.275C207.18 151.014 207.926 150.277 207.926 149.363V148.588C207.926 147.674 207.18 146.937 206.275 146.937Z"
fill="#95DE2D" />
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

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