Compare commits

...

365 Commits

Author SHA1 Message Date
akwizgran
173b6006c4 Don't treat an incoming connection as an attempt to raise the limit. 2020-05-11 17:15:25 +01:00
akwizgran
99edb893f7 Check for stability whenever connections are closed. 2020-05-11 17:15:25 +01:00
akwizgran
f063feedd4 Simplify backoff. 2020-05-11 17:15:25 +01:00
akwizgran
126f515760 Move responsibility for closing connections from limiter to plugin. 2020-05-11 17:15:25 +01:00
akwizgran
e2b61483d6 Always accept incoming connections. 2020-05-11 17:15:25 +01:00
akwizgran
9771825c45 Back off between attempts to raise connection limit. 2020-05-11 17:15:24 +01:00
akwizgran
e376744487 Update constructor args. 2020-05-11 17:15:24 +01:00
akwizgran
13cca9ca61 Occasionally try to raise the limit by allowing an extra connection. 2020-05-11 17:15:24 +01:00
akwizgran
e464f9e7bd Close connections cleanly when starting key agreement. 2020-05-11 17:15:24 +01:00
akwizgran
bd86ff2d5f Let the limiter know whether connections closed cleanly. 2020-05-11 17:15:24 +01:00
akwizgran
bda3b2100a Raise the connection limit if connections are stable. 2020-05-11 17:15:24 +01:00
akwizgran
104a82aea9 Add unit test for connection limiter. 2020-05-11 17:15:24 +01:00
akwizgran
d905451f48 Impose a fixed limit on the number of Bluetooth connections. 2020-05-11 17:15:24 +01:00
Torsten Grote
708452713d Merge branch '1712-detect-dead-bluetooth-connections' into 'master'
Detect and close dead Bluetooth connections

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

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

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

Closes #1722

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

Closes #1714

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

Closes #1696

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

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

Closes #1528 and #1367

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

See merge request briar/briar!1237
2020-03-06 16:52:20 +00:00
akwizgran
8e21068465 Fix misreporting of wifi status in LAN plugin. 2020-03-06 13:35:06 +00:00
akwizgran
4a68e5347d Merge branch '1582-fix-climbing-snackbar' into 'master'
Fix climbing snackbar

Closes #1582

See merge request briar/briar!1223
2020-03-03 14:42:20 +00:00
Torsten Grote
27dd383496 Merge branch '1371-protect-code-cache-directory' into 'master'
Protect cache and code_cache directories when deleting account

Closes #1545 and #1371

See merge request briar/briar!1231
2020-02-26 14:03:39 +00:00
akwizgran
ed50582e27 Show a dialog if the DB key can't be decrypted due to a keystore error. 2020-02-25 15:00:49 +00:00
akwizgran
1546a05568 Catch exception if hardware-backed key can't be loaded. 2020-02-25 12:28:21 +00:00
akwizgran
4bdf966e67 Test that code_cache directory isn't deleted. 2020-02-25 11:23:07 +00:00
akwizgran
e1e67f3b2e Clear the cache directory but don't delete it. 2020-02-25 11:18:50 +00:00
akwizgran
1d63b16ff1 Don't delete the code_cache directory when deleting account.
This seems to avoid the disappearing account bug when installing a new
version.
2020-02-25 10:14:31 +00:00
akwizgran
618ab1f1ec Don't infer anything from existence of (possibly empty) DB directory. 2020-02-24 17:51:59 +00:00
Torsten Grote
421f0ebfa5 Merge branch 'network-prefix-length' into 'master'
Use network prefix length to determine which addresses are connectable

Closes #1178

See merge request briar/briar!1230
2020-02-19 13:11:24 +00:00
akwizgran
61db5d1b04 Make bit-twiddling code more readable. 2020-02-19 09:52:13 +00:00
akwizgran
b3d4012527 Use network prefix length to determine which addresses are connectable. 2020-02-18 11:22:29 +00:00
Torsten Grote
60172331ee Merge branch 'ipv4-link-local' into 'master'
Add support for IPv4 link-local addresses

See merge request briar/briar!1229
2020-02-17 12:42:01 +00:00
akwizgran
076debdc4b Merge branch '1328-reuse-port' into 'master'
Choose port in advance when providing wifi access point

Closes #1328

See merge request briar/briar!1228
2020-02-17 12:37:29 +00:00
akwizgran
ed13cbca6a Add support for IPv4 link-local addresses. 2020-02-17 11:42:13 +00:00
akwizgran
49cb1d0612 Choose port in advance when providing wifi access point. 2020-02-14 16:56:00 +00:00
akwizgran
eb562f8f6b Bump version numbers for 1.2.7 release. 2020-02-14 09:51:14 +00:00
Torsten Grote
d9b3ee7f77 Merge branch '1707-fragment-listeners' into 'master'
Don't overwrite listener references with null during fragment changes

Closes #1707, #1706, #1704, and #1697

See merge request briar/briar!1227
2020-02-13 17:47:09 +00:00
akwizgran
c206b46e28 Don't overwrite listener references with null during fragment changes. 2020-02-13 15:58:26 +00:00
akwizgran
62ef64db11 Bump version numbers for 1.2.6 release. 2020-02-13 11:33:18 +00:00
akwizgran
c2e83dd21d Update translations. 2020-02-13 11:32:19 +00:00
akwizgran
48048dd2fd Merge branch '1483-crash-logging' into 'master'
Log the role we find when failing to parse creator session

See merge request briar/briar!1225
2020-02-12 17:26:32 +00:00
akwizgran
17335811ec Merge branch '1699-no-browser' into 'master'
Check if browser intent resolves before starting

Closes #1699

See merge request briar/briar!1226
2020-02-12 14:48:58 +00:00
Torsten Grote
9946fe806a [android] check if browser intent resolves before starting
This prevents a crash on systems without a browser
2020-02-12 10:43:59 -03:00
Torsten Grote
748d249771 [core] log the role when failing to parse creator session 2020-02-12 09:31:16 -03:00
akwizgran
68d6b4b2ac Merge branch '1665-recyclerview-selection' into 'master'
Upgrade recyclerview and selection library to fix crashes

Closes #1665

See merge request briar/briar!1224
2020-02-12 11:24:22 +00:00
Torsten Grote
cf48efae34 [android] upgrade recyclerview and selection library 2020-02-12 08:02:25 -03:00
akwizgran
287be6aa3f Merge branch '1695-show-no-internet-snackbar-when-tor-disabled' into 'master'
Show "No Internet" snackbar when Tor plugin is not active

Closes #1695

See merge request briar/briar!1222
2020-02-11 17:28:05 +00:00
Torsten Grote
1e4ad67ffc [android] Fix climbing snackbar
Use a fresh snackbar for pending contacts each time it needs to be
shown. Don't re-use the old instance and clear it in onStop().
2020-02-11 13:25:15 -03:00
Torsten Grote
c976dd02ae [android] Show "No Internet" snackbar when Tor plugin is not active 2020-02-11 12:59:28 -03:00
Torsten Grote
c4761c3bb2 Merge branch 'ignore-ble-for-bt-discovery' into 'master'
Ignore BLE-only devices during BT discovery

See merge request briar/briar!1221
2020-02-07 13:18:53 +00:00
Torsten Grote
0ff182b5af Merge branch 'message-tree-thread-safety' into 'master'
Ensure MessageTreeImpl#contains() is thread-safe

See merge request briar/briar!1213
2020-01-23 11:02:55 +00:00
akwizgran
b904b6ea51 Ensure MessageTreeImpl#contains() is thread-safe. 2020-01-23 10:14:35 +00:00
akwizgran
aad92e3f32 Bump version numbers for 1.2.5 release. 2020-01-20 17:28:04 +00:00
akwizgran
f816132ac2 Update translations. 2020-01-20 17:27:11 +00:00
akwizgran
0dcfa5826f Merge branch 'nav-drawer-banner' into 'master'
Hide banner of navigation drawer when not enough screen heigth available

See merge request briar/briar!1208
2020-01-15 13:35:59 +00:00
Torsten Grote
792892d933 Hide banner of navigation drawer when not enough screen heigth available 2020-01-15 09:22:50 -03:00
Torsten Grote
ab9ade9a0b Merge branch '1182-hardware-backed-key' into 'master'
Encrypt DB key with hardware-backed key when available

Closes #1182

See merge request briar/briar!1207
2020-01-15 11:32:33 +00:00
akwizgran
c61c9bbc02 Refactor Android-specific code out of bramble-core. 2020-01-10 17:41:10 +00:00
akwizgran
f650b2236e Catch any Exception when generating stored key. 2020-01-10 16:15:56 +00:00
akwizgran
72a391b506 Break out of loop after generating key. 2020-01-10 12:22:47 +00:00
akwizgran
f76d08c19a Use StrongBox on API 28+ if available. 2020-01-09 15:18:58 +00:00
akwizgran
fc6b596241 Remove unnecessary key purpose. 2020-01-09 15:03:46 +00:00
akwizgran
c11d09a885 Re-encrypt the DB key with the stored key. 2020-01-09 14:45:32 +00:00
akwizgran
d7b05dcba0 Add javadocs. 2020-01-09 14:45:32 +00:00
akwizgran
4d3c1b4fd2 Use Android keystore for encrypting DB key.
Only for new accounts on API 23+.
2020-01-09 14:45:32 +00:00
akwizgran
8a6e886d09 Remove DB key migration code. 2020-01-09 11:22:39 +00:00
Torsten Grote
69093d6786 Merge branch '1636-compression-failure' into 'master'
Fix PNG compression on some phones

Closes #1636

See merge request briar/briar!1183
2020-01-08 16:06:39 +00:00
akwizgran
120fcf550d Merge branch 'undefined' into 'master'
Android TV leanback banner

See merge request briar/briar!1199
2020-01-08 16:00:30 +00:00
akwizgran
5af7bbb24d Merge branch 'master' into '1636-compression-failure'
# Conflicts:
#   bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java
2020-01-08 15:27:01 +00:00
Torsten Grote
dd3c909b31 Merge branch 'bt-address-reflection' into 'master'
Use reflection to get local Bluetooth address

See merge request briar/briar!1202
2020-01-08 14:05:01 +00:00
Torsten Grote
4f1e9067e3 Merge branch '68-enable-private-message-deletion-in-release-builds' into 'master'
Enable private message deletion in release builds

See merge request briar/briar!1206
2020-01-08 14:00:03 +00:00
akwizgran
9305532079 Enable private message deletion in release builds. 2020-01-07 12:43:10 +00:00
akwizgran
bbcdd9418c Revert breaking change to Korean translation. 2020-01-07 12:35:25 +00:00
akwizgran
373ca0e0ea Update translations. 2020-01-07 11:30:21 +00:00
akwizgran
e4af161db5 Merge branch 'fix-test-sensitivity-to-arrival-order' into 'master'
Fix sensitivity of integration tests to message arrival order

See merge request briar/briar!1205
2019-12-20 12:12:36 +00:00
akwizgran
9b37cb19f4 Reset counters before each test. 2019-12-19 17:55:56 +00:00
akwizgran
016178045f Make integration tests less sensitive to sync order. 2019-12-19 17:45:32 +00:00
akwizgran
dae08b2af4 Merge branch '1677-note-alias-key' into 'master'
Add warning about alias key to headless contacts response documentation

See merge request briar/briar!1204
2019-12-17 12:26:16 +00:00
Nico Alt
3bc349e426 Add warning about alias key to headless contacts response documentation
Quick fix for https://code.briarproject.org/briar/briar/issues/1677.
2019-12-16 15:30:02 +01:00
akwizgran
bd478c5074 Ignore BLE-only devices during BT discovery. 2019-12-12 17:24:09 +00:00
akwizgran
26144c18d8 Download test assets, fix expectations. 2019-12-12 14:25:40 +00:00
akwizgran
ace1d38966 Disable PngSuite test by default.
Results are device-dependent and outside our
control.
2019-12-12 14:25:40 +00:00
akwizgran
24a63b08c0 Add transparent PNG. 2019-12-12 14:25:40 +00:00
akwizgran
44411ab224 Simplify code. 2019-12-12 14:25:40 +00:00
akwizgran
6d742c554f Test that PngSuite corrupt test images fail. 2019-12-12 14:25:40 +00:00
akwizgran
1e2ccd96a7 Remove support for GIF attachments on API < 24. 2019-12-12 14:25:40 +00:00
Torsten Grote
1000512c5b [android] Add PngSuite images to compression test 2019-12-12 14:25:37 +00:00
Torsten Grote
c7a0f794c8 [android] Fix PNG compression with different BitmapConfig
Source: https://stackoverflow.com/a/6140360
2019-12-12 10:04:49 +00:00
Torsten Grote
62970cce30 [android] add instrumentation test for image compression 2019-12-12 10:04:44 +00:00
Torsten Grote
6a31274b76 Merge branch 'remove-default-methods' into 'master'
Replace default methods with helper classes

See merge request briar/briar!1203
2019-12-11 16:44:59 +00:00
akwizgran
5962d3c763 Replace default methods with helper classes.
This is a workaround for AbstractMethodErrors
thrown by on-device tests.
2019-12-11 16:28:19 +00:00
Torsten Grote
6f38e70ad2 Merge branch 'tor-0.3.5.9' into 'master'
Upgrade Tor to 0.3.5.9

See merge request briar/briar!1201
2019-12-11 13:10:43 +00:00
akwizgran
61324b1cb0 Catch SecurityException.
This isn't currently thrown, but future versions
of Android might throw it.
2019-12-11 12:03:37 +00:00
akwizgran
d1e21877b3 Use reflection to get local Bluetooth address.
This is expected to work on Android 8 but not 8.1+.
2019-12-11 11:48:53 +00:00
akwizgran
61293c0747 Upgrade Tor to 0.3.5.9. 2019-12-11 09:43:24 +00:00
Torsten Grote
5ba64577bd Merge branch '68-tweak-deletion-error-messages' into 'master'
Tweak the error dialog when message deletion fails

See merge request briar/briar!1200
2019-12-10 16:56:48 +00:00
akwizgran
3486cc8fcc Tweak the error dialog when message deletion fails. 2019-12-10 16:05:03 +00:00
akwizgran
a1357c22b2 Fix checksum for jackson-annotations.
See https://issues.sonatype.org/browse/OSSRH-51881
2019-12-10 13:44:30 +00:00
akwizgran
86693abd66 Merge branch '1656-explain-deletion-failures' into 'master'
Better explain why messages could not be deleted

Closes #1656

See merge request briar/briar!1197
2019-12-10 12:38:07 +00:00
Torsten Grote
0edb2b7b6e [core] Add JavaDoc for MessageRetreiver of IntroductionManager
for message deletion
2019-12-10 09:02:43 -03:00
akwizgran
ffc2c5d900 Merge branch 'javalin-3.5' into 'master'
Upgrade headless to Javalin 3.5

See merge request briar/briar!1162
2019-12-10 10:11:55 +00:00
Hugh Isaacs II
6380133fcd Update briar-android/src/main/AndroidManifest.xml, briar-android/src/main/res/mipmap-xhdpi/tv_banner.png files 2019-12-02 16:29:25 +00:00
Hugh Isaacs II
1ae4062f01 Android TV leanback banner 2019-12-02 16:13:15 +00:00
Torsten Grote
9ebe49b85d [headless] Let websocket upgrade requests pass in AccessManager
This is because JavaScript in browsers apparently can not add Authentication
headers to websocket requests, so we use a dedicated authentication message there.

In Javalin 3, the AccessManager also handles websocket requests.
We need to let those pass to support JavaScript.
2019-11-14 14:13:54 -03:00
Torsten Grote
6f153f14c7 [headless] upgrade to Javalin 3.5 2019-11-14 14:13:54 -03:00
Torsten Grote
5aeee9af8b [core] address review comments for message deletion explanation 2019-11-14 14:05:28 -03:00
akwizgran
52feabecbe Merge branch 'disable-google-tracking' into 'master'
Disable instrumentation test analytics

See merge request briar/briar!1198
2019-11-14 14:07:50 +00:00
Torsten Grote
53fb3f78c1 [android] disable instrumentation test analytics 2019-11-13 14:12:07 -03:00
Torsten Grote
ae0fa351b6 Better explain why messages could not be deleted
This also fixes a bug in the IntroductionManager that would allow to
delete only part of a session's visible messages.
2019-11-11 13:09:51 -03:00
akwizgran
11c43dc7f4 Merge branch '1628-multi-select' into 'master'
Multi-select conversion messages (to delete)

Closes #1628

See merge request briar/briar!1179
2019-11-08 13:05:09 +00:00
Torsten Grote
497ab38be1 [android] highlight selected messages with accent color 2019-11-08 09:54:04 -03:00
Torsten Grote
ddcb412fcd [core] remove notice about removing support for old message type 2019-11-07 15:31:24 -03:00
Torsten Grote
71243ce561 [android] prevent empty state message from showing up briefly when clearing list 2019-11-07 15:30:25 -03:00
Torsten Grote
5c900c443d [core] also support private messages in legacy format for selective deletion 2019-11-07 14:39:50 -03:00
Torsten Grote
97dd9b901d [android] hook up UI to ConversationManager to actually delete messages 2019-11-07 14:39:50 -03:00
Torsten Grote
9ce327a40c [android] highlight selected conversation messages in UI 2019-11-07 14:39:50 -03:00
Torsten Grote
a9b9a8c5f8 [android] allow to select multiple conversation messages 2019-11-07 14:39:49 -03:00
akwizgran
ed66a470cc Merge branch '1643-controller-leaks' into 'master'
Fix controller memory leaks

Closes #1643

See merge request briar/briar!1184
2019-11-07 16:33:18 +00:00
akwizgran
405c243313 Merge branch '1243-attachment-close-button-color' into 'master'
Change image preview close button color to accent color

See merge request briar/briar!1182
2019-11-07 16:23:24 +00:00
Torsten Grote
034e76dd5c [android] Fix controller memory leaks 2019-11-07 12:57:41 -03:00
Torsten Grote
73417a42d6 [android] change image preview close button color to accent color
This was suggested in #1243.
2019-11-07 12:56:52 -03:00
akwizgran
9d72fca2a7 Merge branch 'AndroidX' into 'master'
AndroidX Migration

See merge request briar/briar!1176
2019-11-07 15:40:19 +00:00
akwizgran
b4e0d3b982 Merge branch '1653-npe-settings-fragment' into 'master'
Don't try to display settings before they are fully loaded

Closes #1653

See merge request briar/briar!1194
2019-11-07 15:29:55 +00:00
Torsten Grote
088ca01eb3 [android] re-include BrambleCoreModule since issue caused by it also exists on master 2019-11-07 12:20:03 -03:00
Torsten Grote
83ed6f90d7 [core] downgrade okhttp to 3.12.x series to support our minSdk level 2019-11-07 11:49:45 -03:00
Torsten Grote
ab7b287082 [android] downgrade zxing to 3.3.3 because newer versions need minSdk 24 2019-11-07 10:44:08 -03:00
Torsten Grote
4fe41bfde7 Merge branch 'remove-beta-build-flag' into 'master'
Remove beta build flag

Closes #1527

See merge request briar/briar!1196
2019-11-06 17:53:02 +00:00
Torsten Grote
2e65122e9d Merge branch 'client-versioning-events' into 'master'
Update image attachment UI when new client version is received from contact

Closes #1638

See merge request briar/briar!1195
2019-11-06 17:52:43 +00:00
akwizgran
ee180defce Remove expiry update code, fix references to Briar 1.0. 2019-11-06 14:21:52 +00:00
akwizgran
7ee0febf0c Use TimeUnit to make durations more readable. 2019-11-06 14:21:52 +00:00
akwizgran
216f0598f9 Remove ancient flag for beta builds. 2019-11-06 14:20:11 +00:00
akwizgran
27cba75a50 Fix test expectations. 2019-11-06 14:17:16 +00:00
akwizgran
b3bc5e69b5 Use client versioning event to update UI. 2019-11-06 13:58:11 +00:00
akwizgran
ee1fd2ad8a Broadcast events for client versioning updates. 2019-11-06 13:58:11 +00:00
akwizgran
a844526dae Refactor ClientVersion to bramble-api. 2019-11-06 13:58:06 +00:00
Torsten Grote
cb5a9bdff8 [android] don't try to display settings before they are fully loaded 2019-11-06 09:21:28 -03:00
akwizgran
75dfa80541 Bump version numbers for 1.2.4 release. 2019-11-06 09:58:00 +00:00
akwizgran
41b59fbcfe Merge branch '1610-pending-contacts-offline-snackbar' into 'master'
Don't show offline snackbar when there's no pending contacts

Closes #1610

See merge request briar/briar!1193
2019-11-06 09:50:39 +00:00
akwizgran
98a4f5def1 Merge branch '1654-notification-channel-unavailable' into 'master'
Fail gracefully when ACTION_CHANNEL_NOTIFICATION_SETTINGS is not available

Closes #1654

See merge request briar/briar!1192
2019-11-06 09:49:21 +00:00
akwizgran
aeefa35f38 Merge branch '1454-theme-system-crash' into 'master'
Prevent crash when user has set theme to system default on unsupported API level

Closes #1454

See merge request briar/briar!1191
2019-11-06 09:46:48 +00:00
akwizgran
4e7f33edfd Merge branch '1483-group-invite-not-allowed' into 'master'
Make sure group actions can only be made by the correct role

See merge request briar/briar!1190
2019-11-06 09:45:28 +00:00
akwizgran
f1e957ffed Merge branch '1655-no-bluetooth-activity' into 'master'
Check if REQUEST_BLUETOOTH_DISCOVERABLE is available before launching

Closes #1655

See merge request briar/briar!1189
2019-11-06 09:42:00 +00:00
akwizgran
9e3fed6bc0 Merge branch '1651-no-ringtone-picker' into 'master'
Check if ringtone picker is available before launching it

Closes #1485 and #1651

See merge request briar/briar!1188
2019-11-06 09:40:30 +00:00
Torsten Grote
bf9a39cc6c [android] don't show offline snackbar when there's no pending contacts
If the pending contact list is opened concurrently
with the last pending contact being removed (unlikely but possible)
then the "no internet connection" snackbar would be shown
even though the app is connected to Tor.
2019-11-05 15:35:10 -03:00
Torsten Grote
72aa5397f8 [android] fail gracefully when ACTION_CHANNEL_NOTIFICATION_SETTINGS is not available 2019-11-05 15:17:10 -03:00
Torsten Grote
21eaab3259 [android] prevent crash when user has set theme to system default
on an API level that does not support it.
2019-11-05 14:57:25 -03:00
Torsten Grote
92d595da35 [android] make sure group actions can only be made by the correct role 2019-11-05 14:46:10 -03:00
Torsten Grote
5e85566fc3 [android] check if REQUEST_BLUETOOTH_DISCOVERABLE is available before launching 2019-11-05 12:54:02 -03:00
Torsten Grote
1574bf35fc [android] do not use file:// Uris for notification sounds
This causes a FileUriExposedException otherwise.

Closes #1485
2019-11-05 12:31:25 -03:00
Torsten Grote
533e01e881 [android] check if ringtone picker is available before launching
Also refuse file:// Uri as they cause a FileUriExposedException as in #1485
2019-11-05 12:03:00 -03:00
Torsten Grote
0103835601 [android] upgrade design library to fix memory leak 2019-11-01 14:12:24 -03:00
Torsten Grote
dc1183b4cc [android] Apply updated Android XML layout formatting 2019-11-01 11:47:35 -03:00
Torsten Grote
044e1ebe73 [android] Fix theme of DevReportActivity after AndroidX migration 2019-11-01 11:47:34 -03:00
Torsten Grote
494e51ef07 Optimize imports after AndroidX migration 2019-11-01 11:47:33 -03:00
Torsten Grote
1be078d181 Change CI config so that it finds more breakages
Previously we did not run lint and did not try to compile the
AndroidTest instrumentation tests.
2019-11-01 11:44:37 -03:00
Torsten Grote
98eb78c7bc [android] fix instrumentation tests after AndroidX migration 2019-11-01 11:44:36 -03:00
Torsten Grote
9d31a0a536 [android] Fix robolectric test after AndroidX migration 2019-11-01 11:44:36 -03:00
Torsten Grote
a592c05146 AndroidX migration and library updates 2019-11-01 11:44:36 -03:00
akwizgran
383367f0c8 Merge branch 'remove-remove-contacts-feature-flag' into 'master'
Remove contacts feature flag

See merge request briar/briar!1185
2019-11-01 14:29:37 +00:00
Torsten Grote
ca052ea7dd update translations 2019-11-01 11:12:26 -03:00
Torsten Grote
5147f6b7e6 Remove RemoteContacts feature flag in preparation of 1.2 release 2019-11-01 11:09:43 -03:00
akwizgran
84a8ff1dd8 Merge branch '1629-delete-message-subset' into 'master'
Support for deleting a subset of all conversation messages

Closes #1629

See merge request briar/briar!1180
2019-10-28 16:52:41 +00:00
Torsten Grote
6c489fbea3 [core] also delete attachments when deleting select messages 2019-10-28 10:22:04 -03:00
Torsten Grote
c7200910c9 [core] address feedback for selective conversation message deletion 2019-10-28 09:45:41 -03:00
akwizgran
663e5c4b46 Merge branch '1405-emoji-keyboard' into 'master'
Always show keyboard when clicking text input field

Closes #1405

See merge request briar/briar!1181
2019-10-28 12:16:15 +00:00
Torsten Grote
529eaceec7 [android] show keyboard when clicking text input field 2019-10-22 12:43:35 -03:00
Torsten Grote
f516dbe34f [core] add method to ConversationManager for deleting a set of messages 2019-10-22 11:18:10 -03:00
Torsten Grote
5b515d7e18 [core] implement subset conversation message deletion for IntroductionManager 2019-10-22 11:18:10 -03:00
Torsten Grote
ef04a26cfc [core] implement subset conversation message deletion for GroupInvitationManager 2019-10-22 11:18:09 -03:00
Torsten Grote
2e6fe42074 [core] implement subset conversation message deletion for SharingManager 2019-10-22 11:18:09 -03:00
Torsten Grote
124e2f99b0 [core] Add method to ConversationClient for deleting a set of messages
This also implements the method for MessagingManager
(including integration tests) and adds no-op implementations for other
clients.
2019-10-22 11:18:09 -03:00
Torsten Grote
190a6bff96 [core] Add method to ConversationClient that returns a set of MessageIds it is responsible for 2019-10-22 11:18:08 -03:00
Torsten Grote
01df141c08 Merge branch '843-landscape-keyboard' into 'master'
Raise target API version to 28 and fix soft keyboard issues

Closes #1505

See merge request briar/briar!1043
2019-10-21 12:38:38 +00:00
Torsten Grote
d7c9bf80de Merge branch 'xml-formatting-settings' into 'master'
Update XML code style settings

See merge request briar/briar!1178
2019-10-18 16:51:30 +00:00
akwizgran
3a5e51e248 Update XML code style settings. 2019-10-18 17:38:41 +01:00
akwizgran
a76e3dcec1 Fix bug with enter key when rotating screen. 2019-10-18 14:03:01 +01:00
akwizgran
0fdc7199ed Hide keyboard when contact alias dialog is closed. 2019-10-18 14:03:01 +01:00
akwizgran
248f482fee Use requestFocus tag for RSS import. 2019-10-18 14:03:00 +01:00
akwizgran
4196d046a3 Use stateAlwaysVisible for consistent behaviour. 2019-10-18 14:03:00 +01:00
akwizgran
722ebb22f6 Use requestFocus tag to request initial focus. 2019-10-18 13:45:48 +01:00
akwizgran
a4f561ca1a Request focus when showing soft keyboard. 2019-10-18 13:45:48 +01:00
akwizgran
c7db0bf6fa Remove unused listener implementation. 2019-10-18 13:45:47 +01:00
akwizgran
ca6f458551 Always hide keyboard when importing RSS feed. 2019-10-18 13:45:47 +01:00
akwizgran
c85990408a Remove redundant requestFocus() call. 2019-10-18 13:45:47 +01:00
akwizgran
3ed0204170 Clean up soft input modes. 2019-10-18 13:45:46 +01:00
akwizgran
e2b3340734 Remove redundant methods for showing/hiding keyboard. 2019-10-18 13:45:45 +01:00
akwizgran
78aac8de52 Replace EditText with TextInputEditText. 2019-10-18 13:45:45 +01:00
akwizgran
971ae3a20e Raise target API level to 28. 2019-10-18 13:45:44 +01:00
Torsten Grote
622e7a775a [android] Soft keyboard fixes
1. Manually request focus for input fields and show keyboard

This is needed when targetting API 28 which doesn't give focus anymore
automatically like it used to be.

Closes #1505

2. Remember keyboard states across screen rotations

This also upgrades the emoji library and gets rid of the
KeyboardAwareLinearLayout that is still a relict from the time when we
were using Signal's emoji implementation.

3. Move soft keyboard showing/hiding into UiUtils
2019-10-18 13:44:44 +01:00
akwizgran
103e8482b0 Merge branch 'codeStylesAS3.5' into 'master'
Android Studio 3.5 changed our codeStyles

See merge request briar/briar!1177
2019-10-17 16:56:13 +00:00
Torsten Grote
ddcfc11012 Android Studio 3.5 changed our codeStyles 2019-10-17 13:33:51 -03:00
akwizgran
ab2e40abde Merge branch '1565-duplicate-remote-contacts' into 'master'
UX for handling duplicate handshake links

Closes #1565

See merge request briar/briar!1173
2019-10-16 16:16:08 +00:00
Torsten Grote
1ddceaadd6 Always replace pending contacts no matter their state when link is re-entered 2019-10-16 13:06:21 -03:00
akwizgran
7a644f7d8b Merge branch '1210-fix-list-duplicates' into 'master'
[android] Fix duplicate items in lists

Closes #1210

See merge request briar/briar!1174
2019-10-16 14:32:49 +00:00
Torsten Grote
397afbfec0 Address review comments for detecting duplicate (pending) contacts 2019-10-16 11:15:14 -03:00
Torsten Grote
0d4cb05ac0 [android] fix possible duplicates in list
When doing reloads of list items such as when adding test contacts,
we loaded different versions of those items and added them to the list.
According to the documentation
https://developer.android.com/reference/android/support/v7/util/SortedList.html#add
> If the sorting criteria of the item is changed,
> SortedList won't be able to find its duplicate in the list
> which will result in having a duplicate of the Item in the list.

For the contact list at least, new contacts caused reloads of the entire list
and new messages caused the contacts to be sorted differently.
Thus we ended up with duplicate contacts in the list.

This commit fixes this by replacing the contacts in the list instead of adding them.
It applies the same fix to forums and private groups
which use the same logic and are thus also affected.

Fixes #1210
2019-10-15 16:25:10 -03:00
Torsten Grote
aa0937e6aa [android] Show dialog when (pending) contact already exists
If two different people sent the same link, show warning dialog to the
user to prevent a social attack trying to discover contact
relationships.
2019-10-15 14:47:42 -03:00
Torsten Grote
4bf8d4c0e7 [bramble] add method for getting pending contact state 2019-10-15 14:46:37 -03:00
Torsten Grote
75fcd28071 [bramble] throw exceptions when adding pending contact which exists 2019-10-15 10:32:52 -03:00
Torsten Grote
5f29ab3b40 [bramble-core] Add DB method for getting contact by handshake key 2019-10-15 10:12:59 -03:00
Torsten Grote
f45d00e23c Update translations, add Bosnian and Swahili 2019-10-14 15:11:44 -03:00
akwizgran
2b589c2da6 Merge branch 'tor64' into 'master'
Add support for 64-bit Tor binaries

Closes #1506

See merge request briar/briar!1161
2019-10-14 16:33:53 +00:00
akwizgran
67d15ec82e Merge branch '1633-min-api-16' into 'master'
[android] Raise minimum API level to 16

Closes #1633

See merge request briar/briar!1171
2019-10-14 15:37:43 +00:00
akwizgran
2d44d749ba Merge branch '1627-test-fix' into 'master'
Fix group sharing message deletion test

See merge request briar/briar!1168
2019-10-14 15:36:12 +00:00
Torsten Grote
6ef86c5638 Merge branch 'remove-tor-settings-migration' into 'master'
Remove old migration code for Tor settings

See merge request briar/briar!1172
2019-10-14 15:28:24 +00:00
akwizgran
131f9b9696 Remove old migration code for Tor settings. 2019-10-14 16:00:43 +01:00
akwizgran
a876d4cfb7 Remove a couple of redundant comments. 2019-10-14 15:59:14 +01:00
akwizgran
fafcacf808 Remove a couple more API version checks. 2019-10-14 15:56:44 +01:00
akwizgran
7a0d990f0b Don't include non-PIE binaries in APK.
This shouldn't be merged before raising the minimum
API version to 16.
2019-10-14 15:49:37 +01:00
Torsten Grote
234bdf686e [android] Raise minimum API level to 16 2019-10-14 11:49:06 -03:00
akwizgran
edb9da107f Merge branch '1632-allow-resharing-shareable' into 'master'
Allow sharer to re-share a shareable again after leaving

Closes #1632

See merge request briar/briar!1169
2019-10-14 14:29:38 +00:00
Torsten Grote
d1d4914c6a Merge branch '1582-restore-recycler-view-behaviour' into 'master'
Restore custom layout behaviour for handling snackbar

Closes #1582

See merge request briar/briar!1170
2019-10-14 14:26:01 +00:00
Torsten Grote
9261d23bba [core] allow sharer to re-share a shareable again after leaving 2019-10-14 11:13:01 -03:00
akwizgran
f4febe90c9 Restore custom layout behaviour for handling snackbar. 2019-10-14 14:45:23 +01:00
Torsten Grote
ecd766b204 [core] Fix group sharing message deletion test 2019-10-14 09:40:52 -03:00
akwizgran
ca4fc2dc26 Merge branch '1627-delete-completed-privategroup-sessions' into 'master'
Delete conversation messages belonging to completed private group sessions

Closes #1627

See merge request briar/briar!1167
2019-10-14 11:57:27 +00:00
akwizgran
c3ddcdffe0 Merge branch '1627-delete-completed-sharing-sessions' into 'master'
Delete conversation messages belonging to completed sharing sessions

See merge request briar/briar!1164
2019-10-14 11:45:30 +00:00
Torsten Grote
2e37619357 [android] use new obfs4 release with only pie builds and fixed arm64 2019-10-10 10:01:15 -03:00
Torsten Grote
c247d745df [bramble-android] add support for 64-bit Tor binaries 2019-10-10 09:29:24 -03:00
akwizgran
3a4de3d2cb Merge branch '68-fix-message-tracker' into 'master'
Fix MessageTracker group counts after deleting messages

See merge request briar/briar!1166
2019-10-10 08:54:45 +00:00
Torsten Grote
04f1036dbf [android] Change non-deletion message to refer to ongoing sessions 2019-10-09 17:21:41 -03:00
Torsten Grote
9736f9d31f [core] allow messages from private group sessions with responses get deleted 2019-10-09 17:21:41 -03:00
Torsten Grote
440d5239b1 [core] track GroupCount properly when deleting messages from SharingManager 2019-10-09 13:32:41 -03:00
Torsten Grote
e4a8b10b94 [core] allow messages from shareable sessions with responses get deleted 2019-10-09 13:22:37 -03:00
Torsten Grote
41676065c5 [core] Fix MessageTracker group counts after deleting messages 2019-10-09 13:19:43 -03:00
Torsten Grote
1fcc83a0d0 Merge branch 'feature-flag-message-deletion' into 'master'
Add feature flag for private message deletion

See merge request briar/briar!1165
2019-10-09 15:56:17 +00:00
akwizgran
249b85cd26 Add feature flag for private message deletion. 2019-10-09 16:22:04 +01:00
akwizgran
a23e0699d8 Merge branch '1627-delete-completed-introduction-sessions' into 'master'
Delete conversation messages belonging to completed introduction sessions

See merge request briar/briar!1163
2019-10-09 12:39:05 +00:00
Torsten Grote
e3e47dae48 [core] throw AssertionError if SessionId is missing
Also remove stale comment
2019-10-09 08:27:33 -03:00
Torsten Grote
9660ff2fff [core] delete conversation messages belonging to completed introduction sessions
A session is completed if it returned to the START state
and if all sent messages have been ACKed by the receiver.

The session's metadata is kept in case the user restarts the session
by doing another introduction.
2019-10-09 08:24:16 -03:00
akwizgran
ea810c817b Merge branch '1626-delete-all-messages-ui' into 'master'
Add conversation menu action to delete all messages

Closes #1626

See merge request briar/briar!1159
2019-10-07 16:56:44 +00:00
Torsten Grote
876d50975e [android] fix typo s/can not/cannot/ 2019-10-07 12:08:18 -03:00
akwizgran
bf5bdc52b4 Merge branch '1577-headless-readme-improvements' into 'master'
Clarify minor things in headless readme

Closes #1577

See merge request briar/briar!1157
2019-10-07 14:55:17 +00:00
akwizgran
29320c410e Merge branch '1625-conversation-client-message-deletion' into 'master'
Add ConversationManager method for deleting all messages

Closes #1625

See merge request briar/briar!1158
2019-10-07 14:44:41 +00:00
Nico Alt
d41472a18c Clarify minor things in headless readme
Based on answers received in #1577, I tried to clarify outstanding
questions I had about the Briar Headless API.

Fixes #1577.
2019-10-07 16:35:21 +02:00
akwizgran
c411065255 Merge branch '1582-pending-contacts-snackbar-fab' into 'master'
Use snackbar-aware behaviour for FAB.

Closes #1582

See merge request briar/briar!1156
2019-10-07 13:48:01 +00:00
Torsten Grote
3ac5646355 [briar-android] Add conversation menu action to delete all messages 2019-10-03 15:24:36 -03:00
Torsten Grote
c46fdce277 Add ConversationManager method for deleting all messages
Note that this does not yet delete special conversation messages
such as invitations or introductions and their responses.
2019-10-03 14:47:12 -03:00
akwizgran
643ef593e1 Use dodgeInsetEdges to make room for the snackbar. 2019-10-02 12:16:24 +01:00
akwizgran
eda17449be Merge branch '1582-pending-contacts-snackbar' into 'master'
Prevent pending contacts snackbar from covering contact list

See merge request briar/briar!1152
2019-10-01 12:18:11 +00:00
Torsten Grote
28f82a1507 Use snackbar-aware behaviour for FAB. 2019-10-01 13:01:44 +01:00
Torsten Grote
8734825346 [android] prevent pending contacts snackbar from covering contact list 2019-10-01 08:53:14 -03:00
akwizgran
640f3d63b0 Merge branch '1583-remote-contacts-small-screens' into 'master'
Make Remote Contact layouts work on small screens

Closes #1583

See merge request briar/briar!1155
2019-09-27 14:58:43 +00:00
akwizgran
b1dfd867f0 Bump version numbers for 1.1.9 release. 2019-07-03 12:16:52 +01:00
Torsten Grote
ff76900d74 Merge branch '1609-trimmed-text-length' into 'master'
Use trimmed length when deciding whether text is empty

Closes #1609

See merge request briar/briar!1153
2019-07-01 16:25:15 +00:00
Torsten Grote
945fdb8ee4 [android] Make Remote Contact layouts work on small screens 2019-07-01 17:56:28 +02:00
Torsten Grote
53fe3e1592 Merge branch '1428-android-debug-logging' into 'master'
Enable debug logging for debug and beta builds

Closes #1428

See merge request briar/briar!1154
2019-07-01 14:34:06 +00:00
akwizgran
be76c5b7db Add safety annotations. 2019-07-01 14:38:28 +01:00
akwizgran
909e946e58 Enable debug logging for debug and beta builds. 2019-07-01 14:34:51 +01:00
akwizgran
408d9ddee4 Rename directory for traditional Chinese translation. 2019-07-01 10:14:08 +01:00
akwizgran
0e5027e725 Update list of translations. 2019-07-01 01:35:52 +01:00
akwizgran
2d4c97a69e Update translations, add new translations. 2019-07-01 01:26:27 +01:00
akwizgran
7d62ae5fa8 Use trimmed length when deciding whether text is empty. 2019-07-01 01:13:24 +01:00
Torsten Grote
bd616853cf Merge branch '1607-upgrade-rome' into 'master'
Upgrade Rome to fix memory allocation bug

Closes #1607

See merge request briar/briar!1151
2019-06-28 14:17:46 +00:00
akwizgran
32e1d6c748 Upgrade Rome to fix memory allocation bug. 2019-06-28 15:09:09 +01:00
akwizgran
6b022afa67 Bump version numbers for 1.1.8 release. 2019-06-28 14:48:00 +01:00
akwizgran
e8b454b25b Update translations. 2019-06-28 14:47:03 +01:00
Torsten Grote
54c05b5ffe Merge branch '1606-bump-client-minor-version' into 'master'
Bump client minor version to avoid triggering crash

Closes #1606

See merge request briar/briar!1150
2019-06-28 13:28:37 +00:00
akwizgran
d145a082f5 Bump client minor version to avoid triggering crash. 2019-06-28 14:07:28 +01:00
akwizgran
4fd012c31a Merge branch 'compress-images' into 'master'
Compress images

See merge request briar/briar!1147
2019-06-26 14:21:24 +00:00
akwizgran
95d06770bf Rename 'scale' to 'inSampleSize' for clarity. 2019-06-26 14:36:40 +01:00
akwizgran
428247b7b2 Initialise result LiveData before starting task. 2019-06-26 14:31:40 +01:00
akwizgran
a921361a56 Inject ImageSizeCalculator. 2019-06-26 12:40:28 +01:00
akwizgran
fe7dfa721e Compress image attachments. 2019-06-25 16:55:09 +01:00
akwizgran
92eb06a9e9 Refactor attachment creation to use injection. 2019-06-25 16:29:54 +01:00
Torsten Grote
5beed1a748 Merge branch '1594-preview-fails-to-load' into 'master'
Use a fresh LiveData for each attachment creation task

Closes #1594

See merge request briar/briar!1144
2019-06-20 14:05:43 +00:00
Torsten Grote
774047d856 Merge branch '1585-check-attachment-content-type' into 'master'
Improve handling of missing attachments in UI

See merge request briar/briar!1142
2019-06-20 14:04:02 +00:00
Torsten Grote
fc28e7aa88 Merge branch 'nickname-nitpicks' into 'master'
Nickname nitpicks

See merge request briar/briar!1143
2019-06-20 13:41:25 +00:00
Torsten Grote
78459499b2 Merge branch '1593-qr-code-assertion-error' into 'master'
Keep enum methods used by ZXing

Closes #1593

See merge request briar/briar!1146
2019-06-19 23:45:49 +00:00
akwizgran
c2973608d7 Keep enum methods used by ZXing. 2019-06-19 16:36:39 +01:00
akwizgran
be1c33cb42 Use a fresh LiveData for each attachment creation task. 2019-06-19 13:43:04 +01:00
akwizgran
c955466bda Load missing attachments when they arrive. 2019-06-19 12:47:18 +01:00
akwizgran
593a0c4632 Improve handling of missing and invalid attachments. 2019-06-19 11:23:57 +01:00
akwizgran
ed20b2d8d6 Use attachment header to retrieve attachment. 2019-06-19 10:57:13 +01:00
akwizgran
34583e6d2d Merge branch '1054-crash-scroll' into 'master'
Improve crash screen and reporter

Closes #1426, #1061, #1390, #1012, and #1054

See merge request briar/briar!1049
2019-06-18 16:47:02 +00:00
Torsten Grote
ea5a862242 [android] Fix send button in ReportForm's action bar 2019-06-18 13:28:28 -03:00
akwizgran
9ab9e02f8a Trim whitespace from nicknames (useful for auto-complete). 2019-06-18 17:24:08 +01:00
akwizgran
3f70ae3c8c Use same input type for nicknames everywhere. 2019-06-18 17:19:39 +01:00
Torsten Grote
3f60098099 [android] don't cancel crash reports after sending them 2019-06-18 12:21:04 -03:00
Torsten Grote
e965021e3d [android] don't clear task when submitting feedback, only after crash 2019-06-18 12:21:04 -03:00
Torsten Grote
7d9380d3d6 [android] go to homescreen after pressing back in crash reporter
Fixes #1390
2019-06-18 12:21:04 -03:00
Torsten Grote
3c8c0e579e [android] point ACRA to correct BuildConfig class
Fixes #1061
2019-06-18 12:21:03 -03:00
Torsten Grote
bd2bbe9268 [android] don't show JSON in feedback/crash report
use key-value pairs instead

Closes #1426
2019-06-18 12:21:03 -03:00
Torsten Grote
89d24b1753 [android] Make entire report form scrollable, not only the hidden data 2019-06-18 12:21:03 -03:00
Torsten Grote
861dbe20b1 [android] Fix crash screen buttons to the bottom of the screen
and resize crash icon to the available screen space
2019-06-18 12:21:02 -03:00
Torsten Grote
197800de8b [android] split crash report screen into two fragments 2019-06-18 12:21:02 -03:00
Torsten Grote
07e824ad68 [android] Make crash screen scrollable and add icon 2019-06-18 12:21:01 -03:00
Torsten Grote
d210215bd1 Merge branch '1585-new-messaging-client' into 'master'
Add support for image attachments to messaging client

Closes #1585

See merge request briar/briar!1133
2019-06-18 14:55:40 +00:00
akwizgran
00705447ec Use feature flag to decide which version to advertise. 2019-06-18 13:39:01 +01:00
akwizgran
9095ccef85 Filter attachment URIs in controller. 2019-06-18 13:10:52 +01:00
akwizgran
3196204094 Send legacy private messages from headless app. 2019-06-18 13:03:50 +01:00
akwizgran
2bae639105 Upgrade messaging client to support attachments. 2019-06-18 13:03:49 +01:00
akwizgran
f73d298752 Merge branch 'inject-feature-flags' into 'master'
Use injection to provide feature flags

See merge request briar/briar!1140
2019-06-18 11:51:09 +00:00
Torsten Grote
bc3a443276 Merge branch '1590-create-private-messages-on-ui-thread' into 'master'
Move private message creation off the crypto executor

Closes #1590

See merge request briar/briar!1141
2019-06-18 11:22:45 +00:00
akwizgran
2a29d33303 Move private message creation off the crypto executor. 2019-06-18 12:14:10 +01:00
akwizgran
30e0be9f43 Merge branch '1580-show-snackbar' into 'master'
Show snackbar when there is no internet connection

Closes #1580

See merge request briar/briar!1139
2019-06-18 09:54:34 +00:00
akwizgran
3828d16971 Use injection to provide feature flags. 2019-06-18 10:52:21 +01:00
akwizgran
a54eb64eb5 Merge branch '1468-reject-unsupported-images' into 'master'
Reject unsupported images

Closes #1468

See merge request briar/briar!1038
2019-06-17 16:39:26 +00:00
Torsten Grote
ad2d3e70d6 [android] address thread-safety issues of attachment creation 2019-06-17 13:22:38 -03:00
Torsten Grote
1f91842c52 [android] re-use the same LiveData for AttachmentResults 2019-06-17 13:11:16 -03:00
Torsten Grote
c07a0a2fd7 [android] address review comments for rejecting unsupported images 2019-06-17 13:11:16 -03:00
Torsten Grote
4ee4905e06 [android] migrate added conversation header to new LiveEvent 2019-06-17 13:11:16 -03:00
Torsten Grote
67b7517f2b [android] refactor AttachmentCreator to return a single LiveData 2019-06-17 13:11:16 -03:00
Torsten Grote
cd3174a643 [android] Fix view recycling issue of image previews 2019-06-17 13:11:15 -03:00
Torsten Grote
9d9bc4ca84 [android] Let AttachmentCreator return same LiveData after configuration changes 2019-06-17 13:11:15 -03:00
Torsten Grote
7358091699 [android] Address first round of review comments for attachments 2019-06-17 13:11:15 -03:00
Torsten Grote
11eefaedcf Refactor attachment creation 2019-06-17 13:11:14 -03:00
Torsten Grote
bb5a6c0241 [android] Add assertions to TextAttachmentController 2019-06-17 13:11:14 -03:00
Torsten Grote
70d29af2ba [android] Allow sending message with attachments before previews are loaded 2019-06-17 13:11:14 -03:00
Torsten Grote
baedb14e2b [android] allow attaching only of images with supported mime type 2019-06-17 13:11:13 -03:00
Torsten Grote
2796926709 [android] Load image preview from database instead of content Uri 2019-06-17 13:11:13 -03:00
Torsten Grote
fc6275b037 [android] reject invalid mime types for image attachments 2019-06-17 13:11:13 -03:00
Torsten Grote
f76f9be4ed Reject attachments that exceed the allowed size
Closes #1468
2019-06-17 13:11:13 -03:00
Torsten Grote
6167ba5c46 [android] move unsent attachment cache logic into AttachmentController 2019-06-17 13:11:12 -03:00
Torsten Grote
55f4600a69 [android] Create attachments before showing previews 2019-06-17 13:11:12 -03:00
Torsten Grote
c73801c7e8 [android] Show snackbar when there is no internet connection 2019-06-17 10:11:02 -03:00
Torsten Grote
249e1e28fe Merge branch '1580-offline-state' into 'master'
Add offline state for pending contacts

Closes #1580

See merge request briar/briar!1138
2019-06-17 13:10:41 +00:00
akwizgran
f0cea28aeb Don't show a message for the offline state. 2019-06-17 13:45:22 +01:00
Torsten Grote
32e8ea9888 Merge branch '1565-strings-duplicate-handshake-links' into 'master'
Add strings for duplicate pending contacts

See merge request briar/briar!1137
2019-06-17 12:29:22 +00:00
akwizgran
5a1caed89f Rename endpoints field. 2019-06-17 13:22:36 +01:00
akwizgran
22f5c42fc1 Resolve merge conflicts.
# Conflicts:
#   briar-android/src/main/res/values/strings.xml
2019-06-17 12:13:19 +00:00
akwizgran
aab46040a5 Add comments for translators. 2019-06-17 13:12:11 +01:00
akwizgran
18fd238aa1 Merge branch '1580-strings-offline-state' into 'master'
Add string for pending contact offline state

See merge request briar/briar!1136
2019-06-17 11:12:50 +00:00
akwizgran
3a837b3c5a Resolve merge conflicts.
# Conflicts:
#   briar-android/src/main/res/values/strings.xml
2019-06-17 11:04:11 +00:00
akwizgran
ac2597865c Merge branch '1587-version-negotiation' into 'master'
Add version negotiation to sync protocol

Closes #1587

See merge request briar/briar!1134
2019-06-17 10:54:39 +00:00
akwizgran
4a67cf3ce7 Don't cache default state when adding pending contact.
This can overwrite the initial state broadcast by the
rendezvous poller.
2019-06-17 10:22:08 +01:00
Torsten Grote
a5041e651e Merge branch '1230-strings-adding-contact-slow' into 'master'
Add strings for warning when adding a contact is slow

See merge request briar/briar!1135
2019-06-15 13:37:13 +00:00
akwizgran
b0e97d787f Add offline state for pending contacts. 2019-06-15 12:27:24 +01:00
akwizgran
0d8af780a3 Add strings for duplicate pending contacts. 2019-06-15 11:31:18 +01:00
akwizgran
9c20e6b333 Add string for pending contact offline state. 2019-06-15 11:04:22 +01:00
akwizgran
ab14976c96 Add strings for warning when adding a contact is slow. 2019-06-15 11:01:09 +01:00
akwizgran
ec3f821ba6 Update test expectations. 2019-06-13 17:17:50 +01:00
akwizgran
1d546da781 Store sync versions received from contacts. 2019-06-13 17:07:12 +01:00
akwizgran
f2c951b70b Add DB methods for getting and setting sync versions. 2019-06-13 17:06:57 +01:00
akwizgran
1e259c100d Add sync versions column to contacts table. 2019-06-13 16:35:48 +01:00
akwizgran
3636aeba9a Use HyperSQL-compatible syntax in migration. 2019-06-13 16:34:20 +01:00
akwizgran
132e20a6ce Send versions record at start of each session. 2019-06-13 16:16:02 +01:00
akwizgran
c228e5c219 Add versions record to sync protocol. 2019-06-13 16:16:02 +01:00
akwizgran
ae1d1fc5a7 Add thread safety and null safety annotations. 2019-06-13 16:16:01 +01:00
Torsten Grote
37f02a40e9 Merge branch '1585-temporary-messages' into 'master'
Add support for temporary messages

See merge request briar/briar!1132
2019-06-12 15:39:02 +00:00
akwizgran
3c8b8c39e1 Turn commonly used variables into fields. 2019-06-12 16:29:24 +01:00
akwizgran
8f839e2c30 Remove temporary messages at startup. 2019-06-12 15:21:48 +01:00
akwizgran
da4b63f20f Clean up ValidationManagerImplTest. 2019-06-12 15:17:13 +01:00
akwizgran
cd40e771d2 Allow messages to be marked as temporary. 2019-06-12 15:11:10 +01:00
836 changed files with 19515 additions and 6752 deletions

View File

@@ -17,7 +17,7 @@ test:
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom test
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources
after_script:
# these file change every time but should not be cached

View File

@@ -1,16 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="100" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
@@ -77,7 +71,6 @@
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
@@ -90,7 +83,8 @@
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
@@ -100,7 +94,8 @@
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
@@ -111,6 +106,7 @@
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -121,6 +117,7 @@
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -131,6 +128,7 @@
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -141,6 +139,7 @@
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -151,6 +150,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -161,64 +161,12 @@
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
@@ -226,6 +174,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>

View File

@@ -5,23 +5,31 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion 29
buildToolsVersion '29.0.2'
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 10107
versionName "1.1.7"
minSdkVersion 16
targetSdkVersion 28
versionCode 10207
versionName "1.2.7"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
// FIXME
warning "LintError"
warning "InvalidPackage"
warning "MissingPermission"
warning "InlinedApi", "ObsoleteSdkInt", "Override", "NewApi", "UnusedAttribute"
}
}
configurations {
@@ -30,10 +38,10 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.8@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.9@zip'
tor 'org.briarproject:tor-android:0.3.5.10@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
compileOnly 'javax.annotation:jsr250-api:1.0'
@@ -59,6 +67,8 @@ task unpackTorBinaries {
copy {
from configurations.tor.collect { zipTree(it) }
into torBinariesDir
// TODO: Remove after next Tor upgrade, which won't include non-PIE binaries
include 'geoip.zip', '*_pie.zip'
}
}
dependsOn cleanTorBinaries

View File

@@ -11,4 +11,14 @@ public interface BrambleAndroidEagerSingletons {
void inject(AndroidNetworkModule.EagerSingletons init);
void inject(ReportingModule.EagerSingletons init);
class Helper {
public static void injectEagerSingletons(
BrambleAndroidEagerSingletons c) {
c.inject(new AndroidBatteryModule.EagerSingletons());
c.inject(new AndroidNetworkModule.EagerSingletons());
c.inject(new ReportingModule.EagerSingletons());
}
}
}

View File

@@ -18,10 +18,4 @@ import dagger.Module;
SocksModule.class
})
public class BrambleAndroidModule {
public static void initEagerSingletons(BrambleAndroidEagerSingletons c) {
c.inject(new AndroidBatteryModule.EagerSingletons());
c.inject(new AndroidNetworkModule.EagerSingletons());
c.inject(new ReportingModule.EagerSingletons());
}
}

View File

@@ -12,13 +12,16 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
@@ -29,7 +32,11 @@ class AndroidAccountManager extends AccountManagerImpl
private static final Logger LOG =
Logger.getLogger(AndroidAccountManager.class.getName());
private static final String PREF_DB_KEY = "key";
/**
* Directories that shouldn't be deleted when deleting the user's account.
*/
private static final List<String> PROTECTED_DIR_NAMES =
asList("cache", "code_cache", "lib", "shared_prefs");
protected final Context appContext;
private final SharedPreferences prefs;
@@ -53,36 +60,6 @@ class AndroidAccountManager extends AccountManagerImpl
return exists;
}
// Locking: stateChangeLock
@Override
@Nullable
protected String loadEncryptedDatabaseKey() {
String key = getDatabaseKeyFromPreferences();
if (key == null) key = super.loadEncryptedDatabaseKey();
else migrateDatabaseKeyToFile(key);
return key;
}
// Locking: stateChangeLock
@Nullable
private String getDatabaseKeyFromPreferences() {
String key = prefs.getString(PREF_DB_KEY, null);
if (key == null) LOG.info("No database key in preferences");
else LOG.info("Found database key in preferences");
return key;
}
// Locking: stateChangeLock
private void migrateDatabaseKeyToFile(String key) {
if (storeEncryptedDatabaseKey(key)) {
if (prefs.edit().remove(PREF_DB_KEY).commit())
LOG.info("Database key migrated to file");
else LOG.warning("Database key not removed from preferences");
} else {
LOG.warning("Database key not migrated to file");
}
}
@Override
public void deleteAccount() {
synchronized (stateChangeLock) {
@@ -105,14 +82,14 @@ class AndroidAccountManager extends AccountManagerImpl
return PreferenceManager.getDefaultSharedPreferences(appContext);
}
// Locking: stateChangeLock
@GuardedBy("stateChangeLock")
private void deleteAppData(SharedPreferences... clear) {
// Clear and commit shared preferences
for (SharedPreferences prefs : clear) {
if (!prefs.edit().clear().commit())
LOG.warning("Could not clear shared preferences");
}
// Delete files, except lib and shared_prefs directories
// Delete files, except protected directories
Set<File> files = new HashSet<>();
File dataDir = getDataDir();
@Nullable
@@ -121,14 +98,12 @@ class AndroidAccountManager extends AccountManagerImpl
LOG.warning("Could not list files in app data dir");
} else {
for (File file : fileArray) {
String name = file.getName();
if (!name.equals("lib") && !name.equals("shared_prefs")) {
if (!PROTECTED_DIR_NAMES.contains(file.getName())) {
files.add(file);
}
}
}
files.add(appContext.getFilesDir());
files.add(appContext.getCacheDir());
addIfNotNull(files, appContext.getExternalCacheDir());
if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) {
@@ -140,12 +115,16 @@ class AndroidAccountManager extends AccountManagerImpl
addIfNotNull(files, file);
}
}
// Clear the cache directory but don't delete it
File cacheDir = appContext.getCacheDir();
File[] children = cacheDir.listFiles();
if (children != null) files.addAll(asList(children));
for (File file : files) {
if (LOG.isLoggable(INFO)) {
LOG.info("Deleting " + file.getAbsolutePath());
}
deleteFileOrDir(file);
}
// Recreate the cache dir as some OpenGL drivers expect it to exist
if (!new File(dataDir, "cache").mkdirs())
LOG.warning("Could not recreate cache dir");
}
private File getDataDir() {

View File

@@ -32,6 +32,7 @@ import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -76,9 +77,9 @@ class AndroidNetworkManager implements NetworkManager, Service {
filter.addAction(ACTION_SCREEN_ON);
filter.addAction(ACTION_SCREEN_OFF);
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter);
}
@Override
@@ -136,7 +137,8 @@ class AndroidNetworkManager implements NetworkManager, Service {
}
private boolean isApEvent(@Nullable String action) {
return WIFI_AP_STATE_CHANGED_ACTION.equals(action);
return WIFI_AP_STATE_CHANGED_ACTION.equals(action) ||
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action);
}
}
}

View File

@@ -9,6 +9,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -24,7 +25,6 @@ import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
@@ -47,7 +47,10 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.shuffle;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@@ -74,11 +77,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Clock clock,
Backoff backoff, PluginCallback callback, int maxLatency) {
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
maxLatency);
TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, AndroidExecutor androidExecutor,
Context appContext, Clock clock, Backoff backoff,
PluginCallback callback, int maxLatency, int maxIdleTime) {
super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.clock = clock;
@@ -170,9 +174,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return wrapSocket(ss.accept());
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this,
connectionLimiter, s);
private DuplexTransportConnection wrapSocket(BluetoothSocket s)
throws IOException {
return new AndroidBluetoothTransportConnection(this, connectionLimiter,
timeoutMonitor, s);
}
@Override
@@ -240,11 +245,15 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
break;
} else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
String address = d.getAddress();
if (LOG.isLoggable(INFO))
LOG.info("Discovered " + scrubMacAddress(address));
if (!addresses.contains(address))
addresses.add(address);
// Ignore Bluetooth LE devices
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) {
String address = d.getAddress();
if (LOG.isLoggable(INFO))
LOG.info("Discovered " +
scrubMacAddress(address));
if (!addresses.contains(address))
addresses.add(address);
}
}
now = clock.currentTimeMillis();
}
@@ -260,7 +269,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
appContext.unregisterReceiver(receiver);
}
// Shuffle the addresses so we don't always try the same one first
Collections.shuffle(addresses);
shuffle(addresses);
return addresses;
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.content.Context;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -25,6 +26,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
@@ -35,18 +37,20 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final SecureRandom secureRandom;
private final EventBus eventBus;
private final Clock clock;
private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, Clock clock,
BackoffFactory backoffFactory) {
TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.secureRandom = secureRandom;
this.eventBus = eventBus;
this.clock = clock;
this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory;
}
@@ -63,12 +67,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl();
new BluetoothConnectionLimiterImpl(eventBus, clock);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, ioExecutor, androidExecutor, appContext,
secureRandom, clock, backoff, callback, MAX_LATENCY);
connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
androidExecutor, appContext, clock, backoff,
callback, MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
@@ -10,24 +11,33 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress;
@NotNullByDefault
class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager;
private final BluetoothConnectionLimiter connectionLimiter;
private final BluetoothSocket socket;
private final InputStream in;
AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager,
BluetoothSocket socket) {
BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor, BluetoothSocket socket)
throws IOException {
super(plugin);
this.connectionManager = connectionManager;
this.connectionLimiter = connectionLimiter;
this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2);
String address = socket.getRemoteDevice().getAddress();
if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address);
}
@Override
protected InputStream getInputStream() throws IOException {
return socket.getInputStream();
protected InputStream getInputStream() {
return in;
}
@Override
@@ -40,7 +50,7 @@ class AndroidBluetoothTransportConnection
try {
socket.close();
} finally {
connectionManager.connectionClosed(this);
connectionLimiter.connectionClosed(this, exception);
}
}
}

View File

@@ -19,7 +19,7 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -40,19 +40,6 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
private static final Logger LOG =
getLogger(AndroidLanTcpPlugin.class.getName());
private static final byte[] WIFI_AP_ADDRESS_BYTES =
{(byte) 192, (byte) 168, 43, 1};
private static final InetAddress WIFI_AP_ADDRESS;
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
private final Executor connectionStatusExecutor;
private final ConnectivityManager connectivityManager;
@Nullable
@@ -62,8 +49,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
Backoff backoff, PluginCallback callback, int maxLatency,
int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
int maxIdleTime, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
// Don't execute more than one connection status check at a time
connectionStatusExecutor =
new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1);
@@ -79,6 +67,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@Override
public void start() {
if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty();
running = true;
updateConnectionStatus();
}
@@ -95,16 +84,19 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
}
@Override
protected Collection<InetAddress> getLocalIpAddresses() {
protected List<InetAddress> getUsableLocalInetAddresses() {
// If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList();
// If we're connected to a wifi network, use that network
// If we're connected to a wifi network, return its address
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0)
if (info != null && info.getIpAddress() != 0) {
return singletonList(intToInetAddress(info.getIpAddress()));
}
// If we're running an access point, return its address
if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
return singletonList(WIFI_AP_ADDRESS);
for (InetAddress addr : getLocalInetAddresses()) {
if (addr.equals(WIFI_AP_ADDRESS)) return singletonList(addr);
if (addr.equals(WIFI_DIRECT_AP_ADDRESS)) return singletonList(addr);
}
// No suitable addresses
return emptyList();
}
@@ -144,8 +136,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
private void updateConnectionStatus() {
connectionStatusExecutor.execute(() -> {
if (!running) return;
Collection<InetAddress> addrs = getLocalIpAddresses();
if (addrs.contains(WIFI_AP_ADDRESS)) {
List<InetAddress> addrs = getUsableLocalInetAddresses();
if (addrs.contains(WIFI_AP_ADDRESS)
|| addrs.contains(WIFI_DIRECT_AP_ADDRESS)) {
LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to

View File

@@ -21,10 +21,11 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@NotNullByDefault
public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_LATENCY = 30_000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
@@ -55,7 +56,8 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME);
appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME,
CONNECTION_TIMEOUT);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.plugin.tor;
import android.content.Context;
import android.os.Build;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.event.EventBus;
@@ -89,9 +88,15 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
// Check that we have a Tor binary for this architecture
String architecture = null;
for (String abi : AndroidUtils.getSupportedArchitectures()) {
if (abi.startsWith("x86")) {
if (abi.startsWith("x86_64")) {
architecture = "x86_64";
break;
} else if (abi.startsWith("x86")) {
architecture = "x86";
break;
} else if (abi.startsWith("arm64")) {
architecture = "arm64";
break;
} else if (abi.startsWith("armeabi")) {
architecture = "arm";
break;
@@ -101,8 +106,8 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
LOG.info("Tor is not supported on this architecture");
return null;
}
// Use position-independent executable for SDK >= 16
if (Build.VERSION.SDK_INT >= 16) architecture += "_pie";
// Use position-independent executable
architecture += "_pie";
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);

View File

@@ -23,6 +23,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static android.content.Context.WIFI_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable
@@ -74,8 +75,7 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
// Silence strict mode
StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskWrites();
super.writeSeed();
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT <= 18)
applyOpenSslFix();
if (SDK_INT <= 18) applyOpenSslFix();
StrictMode.setThreadPolicy(tp);
}

View File

@@ -6,15 +6,25 @@ import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@NotNullByDefault
public class AndroidUtils {
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
@@ -22,11 +32,10 @@ public class AndroidUtils {
private static final String STORED_REPORTS = "dev-reports";
@SuppressWarnings("deprecation")
public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>();
if (SDK_INT >= 21) {
abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS));
abis.addAll(asList(Build.SUPPORTED_ABIS));
} else {
abis.add(Build.CPU_ABI);
if (Build.CPU_ABI2 != null) abis.add(Build.CPU_ABI2);
@@ -36,25 +45,76 @@ public class AndroidUtils {
public static String getBluetoothAddress(Context ctx,
BluetoothAdapter adapter) {
return getBluetoothAddressAndMethod(ctx, adapter).getFirst();
}
public static Pair<String, String> getBluetoothAddressAndMethod(Context ctx,
BluetoothAdapter adapter) {
// Return the adapter's address if it's valid and not fake
@SuppressLint("HardwareIds")
String address = adapter.getAddress();
if (isValidBluetoothAddress(address)) return address;
if (isValidBluetoothAddress(address)) {
return new Pair<>(address, "adapter");
}
// Return the address from settings if it's valid and not fake
address = Settings.Secure.getString(ctx.getContentResolver(),
"bluetooth_address");
if (isValidBluetoothAddress(address)) return address;
if (isValidBluetoothAddress(address)) {
return new Pair<>(address, "settings");
}
// Try to get the address via reflection
address = getBluetoothAddressByReflection(adapter);
if (isValidBluetoothAddress(address)) {
return new Pair<>(requireNonNull(address), "reflection");
}
// Let the caller know we can't find the address
return "";
return new Pair<>("", "");
}
private static boolean isValidBluetoothAddress(String address) {
public static boolean isValidBluetoothAddress(@Nullable String address) {
return !StringUtils.isNullOrEmpty(address)
&& BluetoothAdapter.checkBluetoothAddress(address)
&& !address.equals(FAKE_BLUETOOTH_ADDRESS);
}
@Nullable
private static String getBluetoothAddressByReflection(
BluetoothAdapter adapter) {
try {
Field mServiceField =
adapter.getClass().getDeclaredField("mService");
mServiceField.setAccessible(true);
Object mService = mServiceField.get(adapter);
// mService may be null when Bluetooth is disabled
if (mService == null) throw new NoSuchFieldException();
Method getAddressMethod =
mService.getClass().getMethod("getAddress");
return (String) getAddressMethod.invoke(mService);
} catch (NoSuchFieldException e) {
return null;
} catch (IllegalAccessException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
} catch (InvocationTargetException e) {
return null;
} catch (SecurityException e) {
return null;
}
}
public static File getReportDir(Context ctx) {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
}
/**
* Returns an array of supported content types for image attachments.
* GIFs can't be compressed on API < 24 so they're not supported.
* <p>
* TODO: Remove this restriction when large message support is added
*/
public static String[] getSupportedImageContentTypes() {
if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"};
else return new String[] {"image/jpeg", "image/png", "image/gif"};
}
}

View File

@@ -16,13 +16,10 @@ import org.junit.Test;
import java.io.File;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.toHexString;
public class AndroidAccountManagerTest extends BrambleMockTestCase {
@@ -40,11 +37,8 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
private final Application app;
private final ApplicationInfo applicationInfo;
private final String encryptedKeyHex = toHexString(getRandomBytes(123));
private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak");
private final File dbDir = new File(testDir, "db");
private AndroidAccountManager accountManager;
@@ -75,33 +69,12 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
};
}
@Test
public void testDbKeyIsMigratedFromPreferencesToFile() {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(encryptedKeyHex));
oneOf(prefs).edit();
will(returnValue(editor));
oneOf(editor).remove("key");
will(returnValue(editor));
oneOf(editor).commit();
will(returnValue(true));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex,
accountManager.loadEncryptedDatabaseKey());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
}
@Test
public void testDeleteAccountClearsSharedPrefsAndDeletesFiles()
throws Exception {
// Directories 'lib' and 'shared_prefs' should be spared
// Directories 'code_cache', 'lib' and 'shared_prefs' should be spared
File codeCacheDir = new File(testDir, "code_cache");
File codeCacheFile = new File(codeCacheDir, "file");
File libDir = new File(testDir, "lib");
File libFile = new File(libDir, "file");
File sharedPrefsDir = new File(testDir, "shared_prefs");
@@ -140,6 +113,8 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertTrue(dbDir.mkdirs());
assertTrue(keyDir.mkdirs());
assertTrue(codeCacheDir.mkdirs());
assertTrue(codeCacheFile.createNewFile());
assertTrue(libDir.mkdirs());
assertTrue(libFile.createNewFile());
assertTrue(sharedPrefsDir.mkdirs());
@@ -155,6 +130,8 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(dbDir.exists());
assertFalse(keyDir.exists());
assertTrue(codeCacheDir.exists());
assertTrue(codeCacheFile.exists());
assertTrue(libDir.exists());
assertTrue(libFile.exists());
assertTrue(sharedPrefsDir.exists());

View File

@@ -1,44 +1,46 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.android.tools.analytics-library:protos:26.4.0:protos-26.4.0.jar:ad760915586797d39319f402837b378bff3bb4ed583e3e0c48c965631fb2135f',
'com.android.tools.analytics-library:shared:26.4.0:shared-26.4.0.jar:1332106a905d48909c81268c9e414946de3e83487db394c6073b0a9b5c3d0ed2',
'com.android.tools.analytics-library:tracker:26.4.0:tracker-26.4.0.jar:d0020cfbfd4cd75935f2972d6a24089840d4a10df6f3ef2a796093217dd37796',
'com.android.tools.build:apksig:3.4.0:apksig-3.4.0.jar:91d5a1866139c69756280355a6f61b4d619d0516841580114f45a10f2177327e',
'com.android.tools.build:apkzlib:3.4.0:apkzlib-3.4.0.jar:8653c85f5fdf1dde840e8b8af7396aeb79c34b66e541b5860059616006535592',
'com.android.tools.build:builder-model:3.4.0:builder-model-3.4.0.jar:a88f138124a9f016a70bcb4760359a502f65c7deed56507ee4014f4dd9ea853b',
'com.android.tools.build:builder-test-api:3.4.0:builder-test-api-3.4.0.jar:31089ab1ec19ca7687a010867d2f3807513c805b8226979706f4247b5d4df26f',
'com.android.tools.build:builder:3.4.0:builder-3.4.0.jar:476221b5203a7f50089bf185ed95000a34b6f5020ef0a17815afd58606922679',
'com.android.tools.build:gradle-api:3.4.0:gradle-api-3.4.0.jar:215eca38f6719213c2f492b4d622cdd11676c66c9871f8a2aed0c66d00175628',
'com.android.tools.build:manifest-merger:26.4.0:manifest-merger-26.4.0.jar:29e45e690dedd165035e97c21c2ca94d0bd4ec16b6b210daa26669a582b6f220',
'com.android.tools.ddms:ddmlib:26.4.0:ddmlib-26.4.0.jar:93f56fe4630c3166adbd6c51d7bb602d96abb91b07ba5b1165fdcd071e88c940',
'com.android.tools.external.com-intellij:intellij-core:26.4.0:intellij-core-26.4.0.jar:30cb0e879d4424de9677a50b537fb628636b4a50f5470af5e52437980c41421f',
'com.android.tools.external.com-intellij:kotlin-compiler:26.4.0:kotlin-compiler-26.4.0.jar:dd1fe225c31a0e012dc025336363a5b783e2c5c20ffb69e77f8f57e89420d998',
'com.android.tools.external.org-jetbrains:uast:26.4.0:uast-26.4.0.jar:f25f3285b775a983327583ff6584dea54e447813ef69e0ce08b05a45b5f4aab0',
'com.android.tools.layoutlib:layoutlib-api:26.4.0:layoutlib-api-26.4.0.jar:52128f5cf293b224072be361919bfd416e59480ab7264ddcdbbf046b0d7a12e3',
'com.android.tools.lint:lint-api:26.4.0:lint-api-26.4.0.jar:fdb8fca8ae4c254f438338d03d72605e00ed106f2d5550405af41ca1c8509401',
'com.android.tools.lint:lint-checks:26.4.0:lint-checks-26.4.0.jar:4ff52d40488cd3e22b9c6b2eb67784e0c3269d0b42ef9d17689cd75a7b2bceb4',
'com.android.tools.lint:lint-gradle-api:26.4.0:lint-gradle-api-26.4.0.jar:714b7a85c7d2aa10daeab16e969fe7530c659d0728a7f24021da456870418d0f',
'com.android.tools.lint:lint-gradle:26.4.0:lint-gradle-26.4.0.jar:b8c130d273f522388734457e1b96790f41528fcec6fda9e8eaa4e4d95a07cfbb',
'com.android.tools.lint:lint:26.4.0:lint-26.4.0.jar:83aa062fb0405b60ed358d858c8c2955e1bae44a455b498068c6a60988755f00',
'com.android.tools:annotations:26.4.0:annotations-26.4.0.jar:a7955b8e19c3a2a861d6faa43a58b7c0d46ea9112188ee3e235c6f9f439ecc1a',
'com.android.tools:common:26.4.0:common-26.4.0.jar:ea40b94b3c1284ea7700f011388e2906a8363a66abd902891722b3c557984852',
'com.android.tools:dvlib:26.4.0:dvlib-26.4.0.jar:23af89c535b01ba36ceed1b6b309b672814eba624e643cd7dedf0519edad50cc',
'com.android.tools:repository:26.4.0:repository-26.4.0.jar:3d1763ab46199374dc6d94129bba11c70f1d5857e2c81a3ac4898abca40b176b',
'com.android.tools:sdk-common:26.4.0:sdk-common-26.4.0.jar:78a522525b30ffc6b7bf1299c831d24ce385f68a9f4878f8f752e9baefa31b0f',
'com.android.tools:sdklib:26.4.0:sdklib-26.4.0.jar:b854c23892013a326d761cf071c72cf3e038ed0469d10f4a356829fa56e4c132',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.android.tools.analytics-library:protos:26.5.1:protos-26.5.1.jar:8dde1130725461fe827f2a343d353f2b51e8870661fc860d7d5ebddb097ead4e',
'com.android.tools.analytics-library:shared:26.5.1:shared-26.5.1.jar:ccc2f3b00ec17b11401610ba68553544fc8fc517120e84439ac6eb86b875e18d',
'com.android.tools.analytics-library:tracker:26.5.1:tracker-26.5.1.jar:3a76984c0fe2e847ca7a8b35b4780ef0447a9d1666946cb8e60466318e0ab5ae',
'com.android.tools.build:aapt2-proto:0.4.0:aapt2-proto-0.4.0.jar:fac0435e08898f89eeeb9ca236bea707155ff816c12205ced285ad53604133ca',
'com.android.tools.build:apksig:3.5.1:apksig-3.5.1.jar:1fd33e7f009a2a0da766cfeec4211a09f548034b015c289a66d75dd8a9302f4a',
'com.android.tools.build:apkzlib:3.5.1:apkzlib-3.5.1.jar:9f330167cbe973b7db407692f74f4f6453b7ffa5f2048934b06280c2ceee60fa',
'com.android.tools.build:builder-model:3.5.1:builder-model-3.5.1.jar:39ea3c82b76b6e0c9f9fa88d93e0edc1dd4a0f1dfae0ef6fbf2d451da47e5450',
'com.android.tools.build:builder-test-api:3.5.1:builder-test-api-3.5.1.jar:a1b59305584cbcaa078fdc9cfb80871012755b822dd32e8da19add6f7bbcb762',
'com.android.tools.build:builder:3.5.1:builder-3.5.1.jar:e3a8d382434c5f60990730c4719fc814e85a898a33a1e96c1df8d627d3c6eea6',
'com.android.tools.build:gradle-api:3.5.1:gradle-api-3.5.1.jar:be9b41859bace11998f66b04ed944f87e413f3ad6da3c4665587699da125addc',
'com.android.tools.build:manifest-merger:26.5.1:manifest-merger-26.5.1.jar:dcad9ecb967251f4d750f55a4204a2b400e8fbfe5cb930a1d0d5dbe10ae8bdfc',
'com.android.tools.ddms:ddmlib:26.5.1:ddmlib-26.5.1.jar:b081aef2a4ed3f4d47cae4cdb128469735f25a114e026d37123bf9ffdec742a8',
'com.android.tools.external.com-intellij:intellij-core:26.5.1:intellij-core-26.5.1.jar:20eced30adc124805bd93488d9cd9d3e33e6bf7b48e9fe5a703d4983f894d450',
'com.android.tools.external.com-intellij:kotlin-compiler:26.5.1:kotlin-compiler-26.5.1.jar:5aed762dd54875b77ae7018d97c05756ff0c5b9fd02ec595dd396ccd14cc22cb',
'com.android.tools.external.org-jetbrains:uast:26.5.1:uast-26.5.1.jar:4bc8653d6c0943f40fee963a149e36c6baa45683d2530968a13f5007e3c40740',
'com.android.tools.layoutlib:layoutlib-api:26.5.1:layoutlib-api-26.5.1.jar:88732f11396c427273e515d23042e35633f4fe4295528a99b866aa2adf0efd9c',
'com.android.tools.lint:lint-api:26.5.1:lint-api-26.5.1.jar:ec33fcd72bfaf70dd841e03fbfd93f109c2e575aec146067c606689c3972f0de',
'com.android.tools.lint:lint-checks:26.5.1:lint-checks-26.5.1.jar:a1b9607d484aaae7a71dcecdc76f8003d8239af226c776894a2cf63f9e6c60d7',
'com.android.tools.lint:lint-gradle-api:26.5.1:lint-gradle-api-26.5.1.jar:82453fd98a8394cc84ed995c04d2cd744abd1d6589403427ba7eef53115406f3',
'com.android.tools.lint:lint-gradle:26.5.1:lint-gradle-26.5.1.jar:59465b56cf7db77c656d5f8195d721c3d48b6bdd0502d774de335bfe4baff00b',
'com.android.tools.lint:lint:26.5.1:lint-26.5.1.jar:336e4b04ec6f8b0f25879131b7a7862d77df83a1879ee5b71be26128755f8e2e',
'com.android.tools:annotations:26.5.1:annotations-26.5.1.jar:2c43c82f8c59d8f7a61e3239e1a2dc9f69dc342ec09af9b7c9f69b25337c0b6e',
'com.android.tools:common:26.5.1:common-26.5.1.jar:eccfa54486ed54c4e3123cc42195d023bd0dd21bcd2f0e4868e8c6fc70f8ef6b',
'com.android.tools:dvlib:26.5.1:dvlib-26.5.1.jar:46f93ad498b4756e7d867d2fe38c38890a80e7407a4ae459e4a8c8d5c5aeacfe',
'com.android.tools:repository:26.5.1:repository-26.5.1.jar:2b3ee791aa4c3e8ce60498c161a27ca7228816fc630eed4d9f25f2f36a106dce',
'com.android.tools:sdk-common:26.5.1:sdk-common-26.5.1.jar:365f749676c3574676fd465177c8a492f340816db2b520d6ed114d3b6e77bea7',
'com.android.tools:sdklib:26.5.1:sdklib-26.5.1.jar:007da104afb27c8c682a1628023fe9ec438249c8d15ef0fd6624c5bb8e23b696',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.0:gson-2.8.0.jar:c6221763bd79c4f1c3dc7f750b5f29a0bb38b367b81314c4f71896e340c40825',
'com.google.dagger:dagger-compiler:2.22.1:dagger-compiler-2.22.1.jar:e5f28302cbe70a79d3620cddebfb8ec0736814f3980ffe1e673bfe3342f507d3',
'com.google.dagger:dagger-producers:2.22.1:dagger-producers-2.22.1.jar:f834a0082014213a68ff06a0f048d750178d02196c58b0b15beb367d32b97e35',
'com.google.dagger:dagger-spi:2.22.1:dagger-spi-2.22.1.jar:4b0b922793b3bcb91b99fabb75dba77c68afd7ae4c5f0c4fd6ba681f0a291c7d',
'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8',
'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.dagger:dagger-compiler:2.24:dagger-compiler-2.24.jar:3c5afb955fb188da485cb2c048eff37dce0e1530b9780a0f2f7187d16d1ccc1f',
'com.google.dagger:dagger-producers:2.24:dagger-producers-2.24.jar:f10f45b95191954d5d6b043fca9e62fb621d21bf70634b8f8476c7988b504c3a',
'com.google.dagger:dagger-spi:2.24:dagger-spi-2.24.jar:c038445d14dbcb4054e61bf49e05009edf26fce4fdc7ec1a9db544784f68e718',
'com.google.dagger:dagger:2.24:dagger-2.24.jar:550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64',
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
'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:guava:25.0-jre:guava-25.0-jre.jar:3fd4341776428c7e0e5c18a7c10de129475b69ab9d30aeafbb5c277bb6074fa9',
'com.google.guava:guava:26.0-jre:guava-26.0-jre.jar:a0e9cabad665bc20bcd2b01f108e5fc03f756e13aea80abaadb9f407033bea2c',
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:27.0.1-jre:guava-27.0.1-jre.jar:e1c814fd04492a27c38e0317eabeaa1b3e950ec8010239e400fe90ad6c9107b4',
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
'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.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4',
@@ -55,6 +57,7 @@ dependencyVerification {
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'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.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
@@ -66,22 +69,22 @@ dependencyVerification {
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.9:obfs4proxy-android-0.0.9.zip:9b7e9181535ea8d8bbe8ae6338e08cf4c5fc1e357a779393e0ce49586d459ae0',
'org.briarproject:tor-android:0.3.5.8:tor-android-0.3.5.8.zip:42a13a6f185be1a62f42e3f30ce66a3c099ac5ec890a65e7593111b65b44a54a',
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.10:tor-android-0.3.5.10.zip:edd83bf557fcff2105eaa0bdb3f607a6852ebe7360920929ae3039dd5f4774c5',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70',
'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.3.21:kotlin-reflect-1.3.21.jar:a3065c822633191e0a3e3ee12a29bec234fc4b2864a6bb87ef48cce3e9e0c26a',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.21:kotlin-stdlib-common-1.3.21.jar:cea61f7b611895e64f58569a9757fc0ab0d582f107211e1930e0ce2a0add52a7',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21:kotlin-stdlib-jdk7-1.3.21.jar:a87875604fd42140da6938ae4d35ee61081f4482536efc6d2615b8b626a198af',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.21:kotlin-stdlib-jdk8-1.3.21.jar:5823ed66ac122a1c55442ebca5a209a843ccd87f562edc31a787f3d2e47f74d4',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.21:kotlin-stdlib-1.3.21.jar:38ba2370d9f06f50433e06b2ca775b94473c2e2785f410926079ab793c72b034',
'org.jetbrains.kotlin:kotlin-reflect:1.3.50:kotlin-reflect-1.3.50.jar:64583199ea5a54aefd1bd1595288925f784226ee562d1dd279011c6075b3d7a4',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50:kotlin-stdlib-common-1.3.50.jar:8ce678e88e4ba018b66dacecf952471e4d7dfee156a8a819760a5a5ff29d323c',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50:kotlin-stdlib-jdk7-1.3.50.jar:9a026639e76212f8d57b86d55b075394c2e009f1979110751d34c05c5f75d57b',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50:kotlin-stdlib-jdk8-1.3.50.jar:1b351fb6e09c14b55525c74c1f4cf48942eae43c348b7bc764a5e6e423d4da0c',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.50:kotlin-stdlib-1.3.50.jar:e6f05746ee0366d0b52825a090fac474dcf44082c9083bbb205bd16976488d6c',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',

View File

@@ -7,7 +7,7 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
dependencies {
implementation "com.google.dagger:dagger:2.22.1"
implementation "com.google.dagger:dagger:2.24"
implementation 'com.google.code.findbugs:jsr305:3.0.2'
testImplementation 'junit:junit:4.12'

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api;
/**
* Interface for specifying which features are enabled in a build.
*/
public interface FeatureFlags {
boolean shouldEnableImageAttachments();
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -13,7 +14,8 @@ public interface AccountManager {
* Returns true if the manager has the database key. This will be false
* before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and true after {@link #createAccount(String, String)}
* or {@link #signIn(String)} has returned true, until the process exits.
* or {@link #signIn(String)} has returned true, until
* {@link #deleteAccount()} is called or the process exits.
*/
boolean hasDatabaseKey();
@@ -22,25 +24,22 @@ public interface AccountManager {
* before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and non-null after
* {@link #createAccount(String, String)} or {@link #signIn(String)} has
* returned true, until the process exits.
* returned true, until {@link #deleteAccount()} is called or the process
* exits.
*/
@Nullable
SecretKey getDatabaseKey();
/**
* Returns true if the encrypted database key can be loaded from disk, and
* the database directory exists and is a directory.
* Returns true if the encrypted database key can be loaded from disk.
*/
boolean accountExists();
/**
* Creates an identity with the given name and registers it with the
* {@link IdentityManager}. Creates a database key, encrypts it with the
* given password and stores it on disk.
* <p/>
* This method does not create the database directory, so
* {@link #accountExists()} will continue to return false until the
* database directory is created.
* given password and stores it on disk. {@link #accountExists()} will
* return true after this method returns true.
*/
boolean createAccount(String name, String password);
@@ -54,17 +53,19 @@ public interface AccountManager {
* Loads the encrypted database key from disk and decrypts it with the
* given password.
*
* @return true if the database key was successfully loaded and decrypted.
* @throws DecryptionException If the database key could not be loaded and
* decrypted.
*/
boolean signIn(String password);
void signIn(String password) throws DecryptionException;
/**
* Loads the encrypted database key from disk, decrypts it with the old
* password, encrypts it with the new password, and stores it on disk,
* replacing the old key.
*
* @return true if the database key was successfully loaded, re-encrypted
* and stored.
* @throws DecryptionException If the database key could not be loaded and
* decrypted.
*/
boolean changePassword(String oldPassword, String newPassword);
void changePassword(String oldPassword, String newPassword)
throws DecryptionException;
}

View File

@@ -25,7 +25,10 @@ public interface ClientHelper {
throws DbException, FormatException;
void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata,
boolean shared) throws DbException, FormatException;
boolean shared, boolean temporary)
throws DbException, FormatException;
Message createMessage(GroupId g, long timestamp, byte[] body);
Message createMessage(GroupId g, long timestamp, BdfList body)
throws FormatException;
@@ -108,7 +111,7 @@ public interface ClientHelper {
Author parseAndValidateAuthor(BdfList author) throws FormatException;
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
throws FormatException;
throws FormatException;
TransportProperties parseAndValidateTransportProperties(
BdfDictionary properties) throws FormatException;

View File

@@ -4,8 +4,10 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.PendingContactExistsException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
@@ -117,9 +119,14 @@ public interface ContactManager {
* @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(String link, String alias)
throws DbException, FormatException, GeneralSecurityException;
throws DbException, FormatException, GeneralSecurityException,
ContactExistsException, PendingContactExistsException;
/**
* Returns the pending contact with the given ID.

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
public enum PendingContactState {
WAITING_FOR_CONNECTION,
OFFLINE,
CONNECTING,
ADDING_CONTACT,
FAILED

View File

@@ -132,17 +132,33 @@ public interface CryptoComponent {
* storage. The encryption and authentication keys are derived from the
* given password. The ciphertext will be decryptable using the same
* password after the app restarts.
*
* @param keyStrengthener Used to strengthen the password-based key. If
* null, the password-based key will not be strengthened
*/
byte[] encryptWithPassword(byte[] plaintext, String password);
byte[] encryptWithPassword(byte[] plaintext, String password,
@Nullable KeyStrengthener keyStrengthener);
/**
* Decrypts and authenticates the given ciphertext that has been read from
* storage. The encryption and authentication keys are derived from the
* given password. Returns null if the ciphertext cannot be decrypted and
* given password.
*
* @param keyStrengthener Used to strengthen the password-based key. If
* null, or if strengthening was not used when encrypting the ciphertext,
* the password-based key will not be strengthened
* @throws DecryptionException If the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong).
*/
@Nullable
byte[] decryptWithPassword(byte[] ciphertext, String password);
byte[] decryptWithPassword(byte[] ciphertext, String password,
@Nullable KeyStrengthener keyStrengthener)
throws DecryptionException;
/**
* Returns true if the given ciphertext was encrypted using a strengthened
* key. The validity of the ciphertext is not checked.
*/
boolean isEncryptedWithStrengthenedKey(byte[] ciphertext);
/**
* Encrypts the given plaintext to the given public key.

View File

@@ -0,0 +1,17 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class DecryptionException extends Exception {
private final DecryptionResult result;
public DecryptionException(DecryptionResult result) {
this.result = result;
}
public DecryptionResult getDecryptionResult() {
return result;
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.bramble.api.crypto;
/**
* The result of a password-based decryption operation.
*/
public enum DecryptionResult {
/**
* Decryption succeeded.
*/
SUCCESS,
/**
* Decryption failed because the format of the ciphertext was invalid.
*/
INVALID_CIPHERTEXT,
/**
* Decryption failed because the {@link KeyStrengthener} used for
* encryption was not available for decryption.
*/
KEY_STRENGTHENER_ERROR,
/**
* Decryption failed because the password used for decryption did not match
* the password used for encryption.
*/
INVALID_PASSWORD
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Interface for strengthening a password-based key, for example by using a
* key stored in a key management service or hardware security module.
*/
@NotNullByDefault
public interface KeyStrengthener {
/**
* Returns true if the strengthener has been initialised.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
boolean isInitialised();
/**
* Initialises the strengthener if necessary and returns a strong key
* derived from the given key.
*/
SecretKey strengthenKey(SecretKey k);
}

View File

@@ -29,6 +29,7 @@ import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -77,12 +78,12 @@ public interface DatabaseComponent extends TransactionManager {
* Stores a local message.
*/
void addLocalMessage(Transaction txn, Message m, Metadata meta,
boolean shared) throws DbException;
boolean shared, boolean temporary) throws DbException;
/**
* Stores a pending contact.
*/
void addPendingContact(Transaction txn, PendingContact p)
void addPendingContact(Transaction txn, PendingContact p, AuthorId local)
throws DbException;
/**
@@ -427,6 +428,13 @@ public interface DatabaseComponent extends TransactionManager {
*/
Settings getSettings(Transaction txn, String namespace) throws DbException;
/**
* Returns the versions of the sync protocol supported by the given contact.
* <p/>
* Read-only.
*/
List<Byte> getSyncVersions(Transaction txn, ContactId c) throws DbException;
/**
* Returns all transport keys for the given transport.
* <p/>
@@ -510,6 +518,12 @@ public interface DatabaseComponent extends TransactionManager {
void removePendingContact(Transaction txn, PendingContactId p)
throws DbException;
/**
* Removes all temporary messages (and all associated state) from the
* database.
*/
void removeTemporaryMessages(Transaction txn) throws DbException;
/**
* Removes a transport (and all associated state) from the database.
*/
@@ -538,6 +552,11 @@ public interface DatabaseComponent extends TransactionManager {
void setGroupVisibility(Transaction txn, ContactId c, GroupId g,
Visibility v) throws DbException;
/**
* Marks the given message as permanent, i.e. not temporary.
*/
void setMessagePermanent(Transaction txn, MessageId m) throws DbException;
/**
* Marks the given message as shared.
*/
@@ -568,6 +587,12 @@ public interface DatabaseComponent extends TransactionManager {
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
long timePeriod, long base, byte[] bitmap) throws DbException;
/**
* Sets the versions of the sync protocol supported by the given contact.
*/
void setSyncVersions(Transaction txn, ContactId c, List<Byte> supported)
throws DbException;
/**
* Marks the given transport keys as usable for outgoing streams.
*/

View File

@@ -1,13 +1,29 @@
package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import javax.annotation.Nullable;
@NotNullByDefault
public interface DatabaseConfig {
/**
* Returns the directory where the database stores its data.
*/
File getDatabaseDirectory();
/**
* Returns the directory where the encrypted database key is stored.
*/
File getDatabaseKeyDirectory();
/**
* Returns a {@link KeyStrengthener} for strengthening the encryption of
* the database key, or null if no strengthener should be used.
*/
@Nullable
KeyStrengthener getKeyStrengthener();
}

View File

@@ -1,9 +1,21 @@
package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.contact.PendingContact;
/**
* Thrown when a duplicate pending contact is added to the database. This
* exception may occur due to concurrent updates and does not indicate a
* database error.
*/
public class PendingContactExistsException extends DbException {
private final PendingContact pendingContact;
public PendingContactExistsException(PendingContact pendingContact) {
this.pendingContact = pendingContact;
}
public PendingContact getPendingContact() {
return pendingContact;
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.io;
import java.io.InputStream;
public interface TimeoutMonitor {
/**
* Returns an {@link InputStream} that wraps the given stream and allows
* read timeouts to be detected.
*
* @param timeoutMs The read timeout in milliseconds. Timeouts will be
* detected eventually but are not guaranteed to be detected immediately.
*/
InputStream createTimeoutInputStream(InputStream in, long timeoutMs);
}

View File

@@ -4,10 +4,10 @@ public interface LanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.lan");
// a transport property (shared with contacts)
// Transport properties (shared with contacts)
String PROP_IP_PORTS = "ipPorts";
String PROP_PORT = "port";
// a local setting
// A local setting
String PREF_LAN_IP_PORTS = "ipPorts";
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
@@ -14,6 +15,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractDuplexTransportConnection
implements DuplexTransportConnection {
protected final TransportProperties remote = new TransportProperties();
private final Plugin plugin;
private final Reader reader;
private final Writer writer;
@@ -44,6 +47,11 @@ public abstract class AbstractDuplexTransportConnection
return writer;
}
@Override
public TransportProperties getRemoteProperties() {
return remote;
}
private class Reader implements TransportConnectionReader {
@Override

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
/**
* An interface for reading and writing data over a duplex transport. The
@@ -23,4 +24,10 @@ public interface DuplexTransportConnection {
* for writing to the connection.
*/
TransportConnectionWriter getWriter();
/**
* Returns a possibly empty set of {@link TransportProperties} describing
* the remote peer.
*/
TransportProperties getRemoteProperties();
}

View File

@@ -11,4 +11,28 @@ public interface TransportPropertyConstants {
* The maximum length of a property's key or value in UTF-8 bytes.
*/
int MAX_PROPERTY_LENGTH = 100;
/**
* Message metadata key for the transport ID of a local or remote update,
* as a BDF string.
*/
String MSG_KEY_TRANSPORT_ID = "transportId";
/**
* Message metadata key for the version number of a local or remote update,
* as a BDF long.
*/
String MSG_KEY_VERSION = "version";
/**
* Message metadata key for whether an update is local or remote, as a BDF
* boolean.
*/
String MSG_KEY_LOCAL = "local";
/**
* Group metadata key for any discovered transport properties of the
* contact, as a BDF dictionary.
*/
String GROUP_KEY_DISCOVERED = "discovered";
}

View File

@@ -34,6 +34,14 @@ public interface TransportPropertyManager {
void addRemoteProperties(Transaction txn, ContactId c,
Map<TransportId, TransportProperties> props) throws DbException;
/**
* Stores the given properties discovered from an incoming transport
* connection. They will be overridden by any properties received while
* adding the contact or synced from the contact.
*/
void addRemotePropertiesFromConnection(ContactId c, TransportId t,
TransportProperties props) throws DbException;
/**
* Returns the local transport properties for all transports.
*/

View File

@@ -1,10 +1,16 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import javax.annotation.concurrent.Immutable;
/**
* A record acknowledging receipt of one or more {@link Message Messages}.
*/
@Immutable
@NotNullByDefault
public class Ack {
private final Collection<MessageId> acked;

View File

@@ -1,8 +1,14 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@Immutable
@NotNullByDefault
public class Message {
/**

View File

@@ -1,10 +1,16 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import javax.annotation.concurrent.Immutable;
/**
* A record offering the recipient one or more {@link Message Messages}.
*/
@Immutable
@NotNullByDefault
public class Offer {
private final Collection<MessageId> offered;

View File

@@ -9,5 +9,5 @@ public interface RecordTypes {
byte MESSAGE = 1;
byte OFFER = 2;
byte REQUEST = 3;
byte VERSIONS = 4;
}

View File

@@ -1,10 +1,16 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import javax.annotation.concurrent.Immutable;
/**
* A record requesting one or more {@link Message Messages} from the recipient.
*/
@Immutable
@NotNullByDefault
public class Request {
private final Collection<MessageId> requested;

View File

@@ -2,6 +2,9 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.UniqueId;
import java.util.List;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
public interface SyncConstants {
@@ -11,6 +14,11 @@ public interface SyncConstants {
*/
byte PROTOCOL_VERSION = 0;
/**
* The versions of the sync protocol this peer supports.
*/
List<Byte> SUPPORTED_VERSIONS = singletonList(PROTOCOL_VERSION);
/**
* The maximum length of a group descriptor in bytes.
*/
@@ -35,4 +43,10 @@ public interface SyncConstants {
* The maximum number of message IDs in an ack, offer or request record.
*/
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
/**
* The maximum number of versions of the sync protocol a peer may support
* simultaneously.
*/
int MAX_SUPPORTED_VERSIONS = 10;
}

View File

@@ -25,4 +25,7 @@ public interface SyncRecordReader {
Request readRequest() throws IOException;
boolean hasVersions() throws IOException;
Versions readVersions() throws IOException;
}

View File

@@ -15,5 +15,7 @@ public interface SyncRecordWriter {
void writeRequest(Request r) throws IOException;
void writeVersions(Versions v) throws IOException;
void flush() throws IOException;
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.InputStream;
@@ -11,9 +12,9 @@ public interface SyncSessionFactory {
SyncSession createIncomingSession(ContactId c, InputStream in);
SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency,
StreamWriter streamWriter);
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, StreamWriter streamWriter);
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
/**
* A record telling the recipient which versions of the sync protocol the
* sender supports.
*/
@Immutable
@NotNullByDefault
public class Versions {
private final List<Byte> supported;
public Versions(List<Byte> supported) {
this.supported = supported;
}
public List<Byte> getSupportedVersions() {
return supported;
}
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when all sync connections using a given
* transport should be closed.
*/
@Immutable
@NotNullByDefault
public class CloseSyncConnectionsEvent extends Event {
private final TransportId transportId;
public CloseSyncConnectionsEvent(TransportId transportId) {
this.transportId = transportId;
}
public TransportId getTransportId() {
return transportId;
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.List;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the versions of the sync protocol supported
* by a contact are updated.
*/
@Immutable
@NotNullByDefault
public class SyncVersionsUpdatedEvent extends Event {
private final ContactId contactId;
private final List<Byte> supported;
public SyncVersionsUpdatedEvent(ContactId contactId, List<Byte> supported) {
this.contactId = contactId;
this.supported = supported;
}
public ContactId getContactId() {
return contactId;
}
public List<Byte> getSupportedVersions() {
return supported;
}
}

View File

@@ -0,0 +1,63 @@
package org.briarproject.bramble.api.versioning;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ClientVersion implements Comparable<ClientVersion> {
private final ClientMajorVersion majorVersion;
private final int minorVersion;
public ClientVersion(ClientMajorVersion majorVersion,
int minorVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
}
public ClientVersion(ClientId clientId, int majorVersion,
int minorVersion) {
this(new ClientMajorVersion(clientId, majorVersion), minorVersion);
}
public ClientMajorVersion getClientMajorVersion() {
return majorVersion;
}
public ClientId getClientId() {
return majorVersion.getClientId();
}
public int getMajorVersion() {
return majorVersion.getMajorVersion();
}
public int getMinorVersion() {
return minorVersion;
}
@Override
public boolean equals(Object o) {
if (o instanceof ClientVersion) {
ClientVersion cv = (ClientVersion) o;
return majorVersion.equals(cv.majorVersion)
&& minorVersion == cv.minorVersion;
}
return false;
}
@Override
public int hashCode() {
return majorVersion.hashCode();
}
@Override
public int compareTo(ClientVersion cv) {
int compare = majorVersion.compareTo(cv.majorVersion);
if (compare != 0) return compare;
return minorVersion - cv.minorVersion;
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.api.versioning.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.versioning.ClientVersion;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when we receive a client versioning update from
* a contact.
*/
@Immutable
@NotNullByDefault
public class ClientVersionUpdatedEvent extends Event {
private final ContactId contactId;
private final ClientVersion clientVersion;
public ClientVersionUpdatedEvent(ContactId contactId,
ClientVersion clientVersion) {
this.contactId = contactId;
this.clientVersion = clientVersion;
}
public ContactId getContactId() {
return contactId;
}
public ClientVersion getClientVersion() {
return clientVersion;
}
}

View File

@@ -117,4 +117,10 @@ public class IoUtils {
throw new IOException(e);
}
}
public static boolean isNonEmptyDirectory(File f) {
if (!f.isDirectory()) return false;
File[] children = f.listFiles();
return children != null && children.length > 0;
}
}

View File

@@ -2,7 +2,7 @@ dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'com.google.dagger:dagger:2.24:dagger-2.24.jar:550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',

View File

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

View File

@@ -39,18 +39,21 @@ public interface BrambleCoreEagerSingletons {
void inject(VersioningModule.EagerSingletons init);
default void injectBrambleCoreEagerSingletons() {
inject(new ContactModule.EagerSingletons());
inject(new CryptoExecutorModule.EagerSingletons());
inject(new DatabaseExecutorModule.EagerSingletons());
inject(new IdentityModule.EagerSingletons());
inject(new LifecycleModule.EagerSingletons());
inject(new RendezvousModule.EagerSingletons());
inject(new PluginModule.EagerSingletons());
inject(new PropertiesModule.EagerSingletons());
inject(new SystemModule.EagerSingletons());
inject(new TransportModule.EagerSingletons());
inject(new ValidationModule.EagerSingletons());
inject(new VersioningModule.EagerSingletons());
class Helper {
public static void injectEagerSingletons(BrambleCoreEagerSingletons c) {
c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoExecutorModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons());
c.inject(new IdentityModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons());
c.inject(new RendezvousModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons());
c.inject(new ValidationModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons());
}
}
}

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.db.DatabaseModule;
import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule;
@@ -35,6 +36,7 @@ import dagger.Module;
DatabaseExecutorModule.class,
EventModule.class,
IdentityModule.class,
IoModule.class,
KeyAgreementModule.class,
LifecycleModule.class,
PluginModule.class,
@@ -50,8 +52,4 @@ import dagger.Module;
VersioningModule.class
})
public class BrambleCoreModule {
public static void initEagerSingletons(BrambleCoreEagerSingletons c) {
c.injectBrambleCoreEagerSingletons();
}
}

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.Identity;
@@ -16,12 +18,15 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -68,9 +73,10 @@ class AccountManagerImpl implements AccountManager {
return databaseKey;
}
// Locking: stateChangeLock
// Package access for testing
@GuardedBy("stateChangeLock")
@Nullable
protected String loadEncryptedDatabaseKey() {
String loadEncryptedDatabaseKey() {
String key = readDbKeyFromFile(dbKeyFile);
if (key == null) {
LOG.info("No database key in primary file");
@@ -83,7 +89,7 @@ class AccountManagerImpl implements AccountManager {
return key;
}
// Locking: stateChangeLock
@GuardedBy("stateChangeLock")
@Nullable
private String readDbKeyFromFile(File f) {
if (!f.exists()) {
@@ -92,7 +98,7 @@ class AccountManagerImpl implements AccountManager {
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
new FileInputStream(f), Charset.forName("UTF-8")));
String key = reader.readLine();
reader.close();
return key;
@@ -102,8 +108,9 @@ class AccountManagerImpl implements AccountManager {
}
}
// Locking: stateChangeLock
protected boolean storeEncryptedDatabaseKey(String hex) {
// Package access for testing
@GuardedBy("stateChangeLock")
boolean storeEncryptedDatabaseKey(String hex) {
LOG.info("Storing database key in file");
// Create the directory if necessary
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
@@ -140,10 +147,10 @@ class AccountManagerImpl implements AccountManager {
}
}
// Locking: stateChangeLock
@GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes("UTF-8"));
out.write(key.getBytes(Charset.forName("UTF-8")));
out.flush();
out.close();
}
@@ -151,8 +158,7 @@ class AccountManagerImpl implements AccountManager {
@Override
public boolean accountExists() {
synchronized (stateChangeLock) {
return loadEncryptedDatabaseKey() != null
&& databaseConfig.getDatabaseDirectory().isDirectory();
return loadEncryptedDatabaseKey() != null;
}
}
@@ -170,10 +176,11 @@ class AccountManagerImpl implements AccountManager {
}
}
// Locking: stateChangeLock
@GuardedBy("stateChangeLock")
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
byte[] plaintext = key.getBytes();
byte[] ciphertext = crypto.encryptWithPassword(plaintext, password);
byte[] ciphertext = crypto.encryptWithPassword(plaintext, password,
databaseConfig.getKeyStrengthener());
return storeEncryptedDatabaseKey(toHexString(ciphertext));
}
@@ -188,37 +195,41 @@ class AccountManagerImpl implements AccountManager {
}
@Override
public boolean signIn(String password) {
public void signIn(String password) throws DecryptionException {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(password);
if (key == null) return false;
databaseKey = key;
return true;
databaseKey = loadAndDecryptDatabaseKey(password);
}
}
// Locking: stateChangeLock
@Nullable
private SecretKey loadAndDecryptDatabaseKey(String password) {
@GuardedBy("stateChangeLock")
private SecretKey loadAndDecryptDatabaseKey(String password)
throws DecryptionException {
String hex = loadEncryptedDatabaseKey();
if (hex == null) {
LOG.warning("Failed to load encrypted database key");
return null;
throw new DecryptionException(INVALID_CIPHERTEXT);
}
byte[] ciphertext = fromHexString(hex);
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password);
if (plaintext == null) {
LOG.info("Failed to decrypt database key");
return null;
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
keyStrengthener);
SecretKey key = new SecretKey(plaintext);
// If the DB key was encrypted with a weak key and a key strengthener
// is now available, re-encrypt the DB key with a strengthened key
if (keyStrengthener != null &&
!crypto.isEncryptedWithStrengthenedKey(ciphertext)) {
LOG.info("Re-encrypting database key with strengthened key");
encryptAndStoreDatabaseKey(key, password);
}
return new SecretKey(plaintext);
return key;
}
@Override
public boolean changePassword(String oldPassword, String newPassword) {
public void changePassword(String oldPassword, String newPassword)
throws DecryptionException {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
return key != null && encryptAndStoreDatabaseKey(key, newPassword);
encryptAndStoreDatabaseKey(key, newPassword);
}
}
}

View File

@@ -85,14 +85,21 @@ class ClientHelperImpl implements ClientHelper {
@Override
public void addLocalMessage(Message m, BdfDictionary metadata,
boolean shared) throws DbException, FormatException {
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared));
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared,
false));
}
@Override
public void addLocalMessage(Transaction txn, Message m,
BdfDictionary metadata, boolean shared)
BdfDictionary metadata, boolean shared, boolean temporary)
throws DbException, FormatException {
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared);
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared,
temporary);
}
@Override
public Message createMessage(GroupId g, long timestamp, byte[] body) {
return messageFactory.createMessage(g, timestamp, body);
}
@Override

View File

@@ -139,7 +139,8 @@ class ContactManagerImpl implements ContactManager, EventListener {
pendingContactFactory.createPendingContact(link, alias);
Transaction txn = db.startTransaction(false);
try {
db.addPendingContact(txn, p);
AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair);
@@ -147,7 +148,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
} finally {
db.endTransaction(txn);
}
states.put(p.getId(), WAITING_FOR_CONNECTION);
return p;
}

View File

@@ -7,8 +7,10 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
@@ -38,6 +40,9 @@ import static java.lang.System.arraycopy;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -51,7 +56,8 @@ class CryptoComponentImpl implements CryptoComponent {
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
private static final int PBKDF_FORMAT_SCRYPT = 0;
private static final byte PBKDF_FORMAT_SCRYPT = 0;
private static final byte PBKDF_FORMAT_SCRYPT_STRENGTHENED = 1;
private final SecureRandom secureRandom;
private final PasswordBasedKdf passwordBasedKdf;
@@ -311,7 +317,8 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public byte[] encryptWithPassword(byte[] input, String password) {
public byte[] encryptWithPassword(byte[] input, String password,
@Nullable KeyStrengthener keyStrengthener) {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes();
// Generate a random salt
@@ -319,8 +326,9 @@ class CryptoComponentImpl implements CryptoComponent {
secureRandom.nextBytes(salt);
// Calibrate the KDF
int cost = passwordBasedKdf.chooseCostParameter();
// Derive the key from the password
// Derive the encryption key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
if (keyStrengthener != null) key = keyStrengthener.strengthenKey(key);
// Generate a random IV
byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv);
@@ -331,7 +339,9 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] output = new byte[outputLen];
int outputOff = 0;
// Format version
output[outputOff] = PBKDF_FORMAT_SCRYPT;
byte formatVersion = keyStrengthener == null
? PBKDF_FORMAT_SCRYPT : PBKDF_FORMAT_SCRYPT_STRENGTHENED;
output[outputOff] = formatVersion;
outputOff++;
// Salt
arraycopy(salt, 0, output, outputOff, salt.length);
@@ -353,21 +363,26 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
@Nullable
public byte[] decryptWithPassword(byte[] input, String password) {
public byte[] decryptWithPassword(byte[] input, String password,
@Nullable KeyStrengthener keyStrengthener)
throws DecryptionException {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes();
// The input contains the format version, salt, cost parameter, IV,
// ciphertext and MAC
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes)
return null; // Invalid input
+ STORAGE_IV_BYTES + macBytes) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
int inputOff = 0;
// Format version
byte formatVersion = input[inputOff];
inputOff++;
if (formatVersion != PBKDF_FORMAT_SCRYPT)
return null; // Unknown format
// Check whether we support this format version
if (formatVersion != PBKDF_FORMAT_SCRYPT &&
formatVersion != PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
// Salt
byte[] salt = new byte[PBKDF_SALT_BYTES];
arraycopy(input, inputOff, salt, 0, salt.length);
@@ -375,14 +390,22 @@ class CryptoComponentImpl implements CryptoComponent {
// Cost parameter
long cost = ByteUtils.readUint32(input, inputOff);
inputOff += INT_32_BYTES;
if (cost < 2 || cost > Integer.MAX_VALUE)
return null; // Invalid cost parameter
if (cost < 2 || cost > Integer.MAX_VALUE) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
// IV
byte[] iv = new byte[STORAGE_IV_BYTES];
arraycopy(input, inputOff, iv, 0, iv.length);
inputOff += iv.length;
// Derive the key from the password
// Derive the decryption key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
if (formatVersion == PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
if (keyStrengthener == null || !keyStrengthener.isInitialised()) {
// Can't derive the same strengthened key
throw new DecryptionException(KEY_STRENGTHENER_ERROR);
}
key = keyStrengthener.strengthenKey(key);
}
// Initialise the cipher
try {
cipher.init(false, key, iv);
@@ -396,10 +419,16 @@ class CryptoComponentImpl implements CryptoComponent {
cipher.process(input, inputOff, inputLen, output, 0);
return output;
} catch (GeneralSecurityException e) {
return null; // Invalid ciphertext
throw new DecryptionException(INVALID_PASSWORD);
}
}
@Override
public boolean isEncryptedWithStrengthenedKey(byte[] ciphertext) {
return ciphertext.length > 0 &&
ciphertext[0] == PBKDF_FORMAT_SCRYPT_STRENGTHENED;
}
@Override
public byte[] encryptToKey(PublicKey publicKey, byte[] plaintext) {
try {

View File

@@ -33,6 +33,7 @@ import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -115,7 +116,7 @@ interface Database<T> {
* if the message was created locally.
*/
void addMessage(T txn, Message m, MessageState state, boolean shared,
@Nullable ContactId sender) throws DbException;
boolean temporary, @Nullable ContactId sender) throws DbException;
/**
* Adds a dependency between two messages, where the dependent message is
@@ -266,6 +267,16 @@ interface Database<T> {
*/
Collection<ContactId> getContacts(T txn, AuthorId local) throws DbException;
/**
* Returns the contact with the given {@code handshakePublicKey}
* for the given local pseudonym or {@code null} if none exists.
* <p/>
* Read-only.
*/
@Nullable
Contact getContact(T txn, PublicKey handshakePublicKey, AuthorId local)
throws DbException;
/**
* Returns the group with the given ID.
* <p/>
@@ -528,6 +539,13 @@ interface Database<T> {
*/
Settings getSettings(T txn, String namespace) throws DbException;
/**
* Returns the versions of the sync protocol supported by the given contact.
* <p/>
* Read-only.
*/
List<Byte> getSyncVersions(T txn, ContactId c) throws DbException;
/**
* Returns all transport keys for the given transport.
* <p/>
@@ -630,6 +648,12 @@ interface Database<T> {
*/
void removePendingContact(T txn, PendingContactId p) throws DbException;
/**
* Removes all temporary messages (and all associated state) from the
* database.
*/
void removeTemporaryMessages(T txn) throws DbException;
/**
* Removes a transport (and all associated state) from the database.
*/
@@ -671,6 +695,11 @@ interface Database<T> {
void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey,
PrivateKey privateKey) throws DbException;
/**
* Marks the given message as permanent, i.e. not temporary.
*/
void setMessagePermanent(T txn, MessageId m) throws DbException;
/**
* Marks the given message as shared.
*/
@@ -689,6 +718,12 @@ interface Database<T> {
void setReorderingWindow(T txn, KeySetId k, TransportId t,
long timePeriod, long base, byte[] bitmap) throws DbException;
/**
* Sets the versions of the sync protocol supported by the given contact.
*/
void setSyncVersions(T txn, ContactId c, List<Byte> supported)
throws DbException;
/**
* Marks the given transport keys as usable for outgoing streams.
*/

View File

@@ -65,6 +65,7 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.bramble.api.sync.event.SyncVersionsUpdatedEvent;
import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.TransportKeySet;
@@ -273,13 +274,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override
public void addLocalMessage(Transaction transaction, Message m,
Metadata meta, boolean shared) throws DbException {
Metadata meta, boolean shared, boolean temporary)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) {
db.addMessage(txn, m, DELIVERED, shared, null);
db.addMessage(txn, m, DELIVERED, shared, temporary, null);
transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED));
@@ -289,12 +291,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public void addPendingContact(Transaction transaction, PendingContact p)
throws DbException {
public void addPendingContact(Transaction transaction, PendingContact p,
AuthorId local) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (db.containsPendingContact(txn, p.getId()))
throw new PendingContactExistsException();
Contact contact = db.getContact(txn, p.getPublicKey(), local);
if (contact != null)
throw new ContactExistsException(local, contact.getAuthor());
if (db.containsPendingContact(txn, p.getId())) {
PendingContact existing = db.getPendingContact(txn, p.getId());
throw new PendingContactExistsException(existing);
}
db.addPendingContact(txn, p);
transaction.attach(new PendingContactAddedEvent(p));
}
@@ -715,6 +722,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getSettings(txn, namespace);
}
@Override
public List<Byte> getSyncVersions(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getSyncVersions(txn, c);
}
@Override
public Collection<TransportKeySet> getTransportKeys(Transaction transaction,
TransportId t) throws DbException {
@@ -800,7 +816,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId());
} else {
db.addMessage(txn, m, UNKNOWN, false, c);
db.addMessage(txn, m, UNKNOWN, false, false, c);
transaction.attach(new MessageAddedEvent(m, c));
}
transaction.attach(new MessageToAckEvent(c));
@@ -908,6 +924,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new PendingContactRemovedEvent(p));
}
@Override
public void removeTemporaryMessages(Transaction transaction)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
db.removeTemporaryMessages(txn);
}
@Override
public void removeTransport(Transaction transaction, TransportId t)
throws DbException {
@@ -967,6 +991,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}
@Override
public void setMessagePermanent(Transaction transaction, MessageId m)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.setMessagePermanent(txn, m);
}
@Override
public void setMessageShared(Transaction transaction, MessageId m)
throws DbException {
@@ -975,8 +1009,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
if (db.getMessageState(txn, m) != DELIVERED)
throw new IllegalArgumentException(
"Shared undelivered message");
throw new IllegalArgumentException("Shared undelivered message");
db.setMessageShared(txn, m);
transaction.attach(new MessageSharedEvent(m));
}
@@ -1028,6 +1061,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap);
}
@Override
public void setSyncVersions(Transaction transaction, ContactId c,
List<Byte> supported) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
db.setSyncVersions(txn, c, supported);
transaction.attach(new SyncVersionsUpdatedEvent(c, supported));
}
@Override
public void setTransportKeysActive(Transaction transaction, TransportId t,
KeySetId k) throws DbException {

View File

@@ -25,6 +25,7 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
/**
@@ -69,8 +70,9 @@ class H2Database extends JdbcDatabase {
LOG.info("Contents of account directory before opening DB:");
logFileOrDir(LOG, INFO, dir.getParentFile());
}
boolean reopen = !dir.mkdirs();
boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.h2.Driver", reopen, key, listener);
if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory after opening DB:");

View File

@@ -20,9 +20,11 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
/**
* Contains all the HSQLDB-specific code for the database.
@@ -64,7 +66,10 @@ class HyperSqlDatabase extends JdbcDatabase {
public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException {
this.key = key;
boolean reopen = !config.getDatabaseDirectory().mkdirs();
File dir = config.getDatabaseDirectory();
boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen);
if (!reopen && dir.mkdirs()) LOG.info("Created database directory");
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener);
return reopen;
}

View File

@@ -62,6 +62,7 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import static java.sql.Types.BINARY;
import static java.sql.Types.BOOLEAN;
@@ -97,7 +98,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing
static final int CODE_SCHEMA_VERSION = 45;
static final int CODE_SCHEMA_VERSION = 47;
// Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1;
@@ -134,6 +135,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " handshakePublicKey _BINARY," // Null if key is unknown
+ " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL,"
+ " syncVersions _BINARY DEFAULT '00' NOT NULL,"
+ " PRIMARY KEY (contactId),"
+ " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)"
@@ -177,6 +179,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " timestamp BIGINT NOT NULL,"
+ " state INT NOT NULL,"
+ " shared BOOLEAN NOT NULL,"
+ " temporary BOOLEAN NOT NULL,"
+ " length INT NOT NULL,"
+ " raw BLOB," // Null if message has been deleted
+ " PRIMARY KEY (messageId),"
@@ -336,25 +339,26 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final Logger LOG =
getLogger(JdbcDatabase.class.getName());
// Different database libraries use different names for certain types
private final MessageFactory messageFactory;
private final Clock clock;
private final DatabaseTypes dbTypes;
// Locking: connectionsLock
private final Lock connectionsLock = new ReentrantLock();
private final Condition connectionsChanged = connectionsLock.newCondition();
@GuardedBy("connectionsLock")
private final LinkedList<Connection> connections = new LinkedList<>();
private int openConnections = 0; // Locking: connectionsLock
private boolean closed = false; // Locking: connectionsLock
@GuardedBy("connectionsLock")
private int openConnections = 0;
@GuardedBy("connectionsLock")
private boolean closed = false;
protected abstract Connection createConnection()
throws DbException, SQLException;
protected abstract void compactAndClose() throws DbException;
private final Lock connectionsLock = new ReentrantLock();
private final Condition connectionsChanged = connectionsLock.newCondition();
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
Clock clock) {
this.dbTypes = databaseTypes;
@@ -457,7 +461,9 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration41_42(dbTypes),
new Migration42_43(dbTypes),
new Migration43_44(dbTypes),
new Migration44_45()
new Migration44_45(),
new Migration45_46(),
new Migration46_47(dbTypes)
);
}
@@ -777,22 +783,23 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override
public void addMessage(Connection txn, Message m, MessageState state,
boolean messageShared, @Nullable ContactId sender)
boolean shared, boolean temporary, @Nullable ContactId sender)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
+ " state, shared, length, raw)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
+ " state, shared, temporary, length, raw)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getId().getBytes());
ps.setBytes(2, m.getGroupId().getBytes());
ps.setLong(3, m.getTimestamp());
ps.setInt(4, state.getValue());
ps.setBoolean(5, messageShared);
ps.setBoolean(5, shared);
ps.setBoolean(6, temporary);
byte[] raw = messageFactory.getRawMessage(m);
ps.setInt(6, raw.length);
ps.setBytes(7, raw);
ps.setInt(7, raw.length);
ps.setBytes(8, raw);
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
@@ -804,8 +811,7 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean offered = removeOfferedMessage(txn, c, m.getId());
boolean seen = offered || c.equals(sender);
addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(),
raw.length, state, e.getValue(), messageShared,
false, seen);
raw.length, state, e.getValue(), shared, false, seen);
}
// Update denormalised column in messageDependencies if dependency
// is in same group as dependent
@@ -1459,6 +1465,47 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Nullable
@Override
public Contact getContact(Connection txn, PublicKey handshakePublicKey,
AuthorId localAuthorId) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId, authorId, formatVersion, name,"
+ " alias, publicKey, verified"
+ " FROM contacts"
+ " WHERE handshakePublicKey = ? AND localAuthorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, handshakePublicKey.getEncoded());
ps.setBytes(2, localAuthorId.getBytes());
rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
return null;
}
ContactId contactId = new ContactId(rs.getInt(1));
AuthorId authorId = new AuthorId(rs.getBytes(2));
int formatVersion = rs.getInt(3);
String name = rs.getString(4);
String alias = rs.getString(5);
PublicKey publicKey = new SignaturePublicKey(rs.getBytes(6));
boolean verified = rs.getBoolean(7);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
Author author =
new Author(authorId, formatVersion, name, publicKey);
return new Contact(contactId, author, localAuthorId, alias,
handshakePublicKey, verified);
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public Group getGroup(Connection txn, GroupId g) throws DbException {
PreparedStatement ps = null;
@@ -2324,6 +2371,32 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public List<Byte> getSyncVersions(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT syncVersions FROM contacts"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
byte[] bytes = rs.getBytes(1);
List<Byte> supported = new ArrayList<>(bytes.length);
for (byte b : bytes) supported.add(b);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return supported;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public Collection<TransportKeySet> getTransportKeys(Connection txn,
TransportId t) throws DbException {
@@ -2876,6 +2949,21 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void removeTemporaryMessages(Connection txn) throws DbException {
Statement s = null;
try {
String sql = "DELETE FROM messages WHERE temporary = TRUE";
s = txn.createStatement();
int affected = s.executeUpdate(sql);
if (affected < 0) throw new DbStateException();
s.close();
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void removeTransport(Connection txn, TransportId t)
throws DbException {
@@ -3021,6 +3109,24 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void setMessagePermanent(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages SET temporary = FALSE"
+ " WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void setMessageShared(Connection txn, MessageId m)
throws DbException {
@@ -3124,6 +3230,29 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void setSyncVersions(Connection txn, ContactId c,
List<Byte> supported) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE contacts SET syncVersions = ?"
+ " WHERE contactId = ?";
ps = txn.prepareStatement(sql);
byte[] bytes = new byte[supported.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = supported.get(i);
}
ps.setBytes(1, bytes);
ps.setInt(2, c.getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void setTransportKeysActive(Connection txn, TransportId t,
KeySetId k) throws DbException {

View File

@@ -0,0 +1,40 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration45_46 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration45_46.class.getName());
@Override
public int getStartVersion() {
return 45;
}
@Override
public int getEndVersion() {
return 46;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute("ALTER TABLE messages"
+ " ADD COLUMN temporary BOOLEAN DEFAULT FALSE NOT NULL");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration46_47 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration46_47.class.getName());
private final DatabaseTypes dbTypes;
Migration46_47(DatabaseTypes dbTypes) {
this.dbTypes = dbTypes;
}
@Override
public int getStartVersion() {
return 46;
}
@Override
public int getEndVersion() {
return 47;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute(dbTypes.replaceTypes("ALTER TABLE contacts"
+ " ADD COLUMN syncVersions"
+ " _BINARY DEFAULT '00' NOT NULL"));
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class IoModule {
@Provides
@Singleton
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
return timeoutMonitor;
}
}

View File

@@ -0,0 +1,104 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.concurrent.GuardedBy;
@NotNullByDefault
class TimeoutInputStream extends InputStream {
private final Clock clock;
private final InputStream in;
private final long timeoutMs;
private final CloseListener listener;
private final Object lock = new Object();
@GuardedBy("lock")
private long readStartedMs = -1;
TimeoutInputStream(Clock clock, InputStream in, long timeoutMs,
CloseListener listener) {
this.clock = clock;
this.in = in;
this.timeoutMs = timeoutMs;
this.listener = listener;
}
@Override
public int read() throws IOException {
synchronized (lock) {
readStartedMs = clock.currentTimeMillis();
}
int input = in.read();
synchronized (lock) {
readStartedMs = -1;
}
return input;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
synchronized (lock) {
readStartedMs = clock.currentTimeMillis();
}
int read = in.read(b, off, len);
synchronized (lock) {
readStartedMs = -1;
}
return read;
}
@Override
public void close() throws IOException {
try {
in.close();
} finally {
listener.onClose(this);
}
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public boolean markSupported() {
return in.markSupported();
}
@Override
public void reset() throws IOException {
in.reset();
}
@Override
public long skip(long n) throws IOException {
return in.skip(n);
}
boolean hasTimedOut() {
synchronized (lock) {
return readStartedMs != -1 &&
clock.currentTimeMillis() - readStartedMs > timeoutMs;
}
}
interface CloseListener {
void onClose(TimeoutInputStream closed);
}
}

View File

@@ -0,0 +1,96 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
class TimeoutMonitorImpl implements TimeoutMonitor {
private static final Logger LOG =
getLogger(TimeoutMonitorImpl.class.getName());
private static final long CHECK_INTERVAL_MS = SECONDS.toMillis(10);
private final ScheduledExecutorService scheduler;
private final Executor ioExecutor;
private final Clock clock;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<TimeoutInputStream> streams = new ArrayList<>();
@GuardedBy("lock")
private Future<?> task = null;
@Inject
TimeoutMonitorImpl(@Scheduler ScheduledExecutorService scheduler,
@IoExecutor Executor ioExecutor, Clock clock) {
this.scheduler = scheduler;
this.ioExecutor = ioExecutor;
this.clock = clock;
}
@Override
public InputStream createTimeoutInputStream(InputStream in,
long timeoutMs) {
TimeoutInputStream stream = new TimeoutInputStream(clock, in,
timeoutMs, this::removeStream);
synchronized (lock) {
if (streams.isEmpty()) {
task = scheduler.scheduleWithFixedDelay(this::checkTimeouts,
CHECK_INTERVAL_MS, CHECK_INTERVAL_MS, MILLISECONDS);
}
streams.add(stream);
}
return stream;
}
private void removeStream(TimeoutInputStream stream) {
Future<?> toCancel = null;
synchronized (lock) {
if (streams.remove(stream) && streams.isEmpty()) {
toCancel = task;
task = null;
}
}
if (toCancel != null) toCancel.cancel(false);
}
@Scheduler
private void checkTimeouts() {
ioExecutor.execute(() -> {
List<TimeoutInputStream> snapshot;
synchronized (lock) {
snapshot = new ArrayList<>(streams);
}
for (TimeoutInputStream stream : snapshot) {
if (stream.hasTimedOut()) {
LOG.info("Input stream has timed out");
try {
stream.close();
} catch (IOException e) {
logException(LOG, INFO, e);
}
}
}
});
}
}

View File

@@ -107,8 +107,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
else logDuration(LOG, "Creating database", start);
db.transaction(false, txn -> {
long start1 = now();
db.removeTemporaryMessages(txn);
logDuration(LOG, "Removing temporary messages", start1);
for (OpenDatabaseHook hook : openDatabaseHooks) {
long start1 = now();
start1 = now();
hook.onDatabaseOpened(txn);
if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Calling open database hook "

View File

@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
@@ -52,6 +54,7 @@ class ConnectionManagerImpl implements ConnectionManager {
private final HandshakeManager handshakeManager;
private final ContactExchangeManager contactExchangeManager;
private final ConnectionRegistry connectionRegistry;
private final TransportPropertyManager transportPropertyManager;
@Inject
ConnectionManagerImpl(@IoExecutor Executor ioExecutor,
@@ -60,7 +63,8 @@ class ConnectionManagerImpl implements ConnectionManager {
SyncSessionFactory syncSessionFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionRegistry connectionRegistry) {
ConnectionRegistry connectionRegistry,
TransportPropertyManager transportPropertyManager) {
this.ioExecutor = ioExecutor;
this.keyManager = keyManager;
this.streamReaderFactory = streamReaderFactory;
@@ -69,6 +73,7 @@ class ConnectionManagerImpl implements ConnectionManager {
this.handshakeManager = handshakeManager;
this.contactExchangeManager = contactExchangeManager;
this.connectionRegistry = connectionRegistry;
this.transportPropertyManager = transportPropertyManager;
}
@Override
@@ -125,8 +130,8 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createSimplexOutgoingSession(c,
return syncSessionFactory.createSimplexOutgoingSession(
requireNonNull(ctx.getContactId()), ctx.getTransportId(),
w.getMaxLatency(), streamWriter);
}
@@ -134,8 +139,8 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createDuplexOutgoingSession(c,
return syncSessionFactory.createDuplexOutgoingSession(
requireNonNull(ctx.getContactId()), ctx.getTransportId(),
w.getMaxLatency(), w.getMaxIdleTime(), streamWriter);
}
@@ -269,6 +274,7 @@ class ConnectionManagerImpl implements ConnectionManager {
private final TransportId transportId;
private final TransportConnectionReader reader;
private final TransportConnectionWriter writer;
private final TransportProperties remote;
@Nullable
private volatile SyncSession outgoingSession = null;
@@ -278,6 +284,7 @@ class ConnectionManagerImpl implements ConnectionManager {
this.transportId = transportId;
reader = connection.getReader();
writer = connection.getWriter();
remote = connection.getRemoteProperties();
}
@Override
@@ -313,13 +320,16 @@ class ConnectionManagerImpl implements ConnectionManager {
// Start the outgoing session on another thread
ioExecutor.execute(() -> runOutgoingSession(contactId));
try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// Create and run the incoming session
createIncomingSession(ctx, reader).run();
reader.dispose(false, true);
// Interrupt the outgoing session so it finishes cleanly
SyncSession out = outgoingSession;
if (out != null) out.interrupt();
} catch (IOException e) {
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
onReadError(true);
} finally {
@@ -375,6 +385,7 @@ class ConnectionManagerImpl implements ConnectionManager {
private final TransportId transportId;
private final TransportConnectionReader reader;
private final TransportConnectionWriter writer;
private final TransportProperties remote;
@Nullable
private volatile SyncSession outgoingSession = null;
@@ -385,6 +396,7 @@ class ConnectionManagerImpl implements ConnectionManager {
this.transportId = transportId;
reader = connection.getReader();
writer = connection.getWriter();
remote = connection.getRemoteProperties();
}
@Override
@@ -461,13 +473,16 @@ class ConnectionManagerImpl implements ConnectionManager {
connectionRegistry.registerConnection(contactId, transportId,
false);
try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// Create and run the incoming session
createIncomingSession(ctx, reader).run();
reader.dispose(false, true);
// Interrupt the outgoing session so it finishes cleanly
SyncSession out = outgoingSession;
if (out != null) out.interrupt();
} catch (IOException e) {
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
onReadError();
} finally {

View File

@@ -3,9 +3,30 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
@NotNullByDefault
interface BluetoothConnectionLimiter {
/**
* How long a connection must remain open before it's considered stable.
*/
long STABILITY_PERIOD_MS = SECONDS.toMillis(90);
/**
* The minimum interval between attempts to raise the connection limit.
* This is longer than {@link #STABILITY_PERIOD_MS} so we don't start
* another attempt before knowing the outcome of the last one.
*/
long MIN_ATTEMPT_INTERVAL_MS = MINUTES.toMillis(2);
/**
* The maximum interval between attempts to raise the connection limit.
*/
long MAX_ATTEMPT_INTERVAL_MS = DAYS.toMillis(2);
/**
* Informs the limiter that key agreement has started.
*/
@@ -23,12 +44,12 @@ interface BluetoothConnectionLimiter {
boolean canOpenContactConnection();
/**
* Informs the limiter that a contact connection has been opened. The
* limiter may close the new connection if key agreement is in progress.
* Informs the limiter that a contact connection has been opened.
* <p/>
* Returns false if the limiter has closed the new connection.
* Returns true if the connection is allowed.
*/
boolean contactConnectionOpened(DuplexTransportConnection conn);
boolean contactConnectionOpened(DuplexTransportConnection conn,
boolean incoming);
/**
* Informs the limiter that a key agreement connection has been opened.
@@ -37,11 +58,13 @@ interface BluetoothConnectionLimiter {
/**
* Informs the limiter that the given connection has been closed.
*
* @param exception True if the connection was closed due to an exception.
*/
void connectionClosed(DuplexTransportConnection conn);
void connectionClosed(DuplexTransportConnection conn, boolean exception);
/**
* Informs the limiter that all connections have been closed.
* Informs the limiter that the Bluetooth adapter has been disabled.
*/
void allConnectionsClosed();
void bluetoothDisabled();
}

View File

@@ -1,46 +1,59 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@NotNullByDefault
@ThreadSafe
class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
private static final Logger LOG =
Logger.getLogger(BluetoothConnectionLimiterImpl.class.getName());
getLogger(BluetoothConnectionLimiterImpl.class.getName());
private final EventBus eventBus;
private final Clock clock;
private final Object lock = new Object();
// The following are locking: lock
private final LinkedList<DuplexTransportConnection> connections =
new LinkedList<>();
@GuardedBy("lock")
private final List<ConnectionRecord> connections = new LinkedList<>();
@GuardedBy("lock")
private boolean keyAgreementInProgress = false;
@GuardedBy("lock")
private int connectionLimit = 1;
@GuardedBy("lock")
private long timeOfLastAttempt = 0,
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
@Inject
BluetoothConnectionLimiterImpl(EventBus eventBus, Clock clock) {
this.eventBus = eventBus;
this.clock = clock;
}
@Override
public void keyAgreementStarted() {
List<DuplexTransportConnection> close;
synchronized (lock) {
keyAgreementInProgress = true;
close = new ArrayList<>(connections);
connections.clear();
}
if (LOG.isLoggable(INFO)) {
LOG.info("Key agreement started, closing " + close.size() +
" connections");
}
for (DuplexTransportConnection conn : close) tryToClose(conn);
LOG.info("Key agreement started");
eventBus.broadcast(new CloseSyncConnectionsEvent(ID));
}
@Override
@@ -55,62 +68,128 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
public boolean canOpenContactConnection() {
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Can't open contact connection during key agreement");
LOG.info("Refusing contact connection during key agreement");
return false;
} else {
LOG.info("Can open contact connection");
return true;
long now = clock.currentTimeMillis();
return isContactConnectionAllowedByLimit(now);
}
}
}
@Override
public boolean contactConnectionOpened(DuplexTransportConnection conn) {
boolean accept = true;
public boolean contactConnectionOpened(DuplexTransportConnection conn,
boolean incoming) {
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Refusing contact connection during key agreement");
accept = false;
return false;
} else {
LOG.info("Accepting contact connection");
connections.add(conn);
long now = clock.currentTimeMillis();
if (incoming || isContactConnectionAllowedByLimit(now)) {
connections.add(new ConnectionRecord(conn, now));
if (!incoming && connections.size() > connectionLimit) {
LOG.info("Attempting to raise connection limit");
timeOfLastAttempt = now;
}
return true;
} else {
return false;
}
}
}
if (!accept) tryToClose(conn);
return accept;
}
@Override
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
synchronized (lock) {
LOG.info("Accepting key agreement connection");
connections.add(conn);
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getWriter().dispose(false);
conn.getReader().dispose(false, false);
} catch (IOException e) {
logException(LOG, WARNING, e);
connections.add(
new ConnectionRecord(conn, clock.currentTimeMillis()));
}
}
@Override
public void connectionClosed(DuplexTransportConnection conn) {
public void connectionClosed(DuplexTransportConnection conn,
boolean exception) {
synchronized (lock) {
connections.remove(conn);
Iterator<ConnectionRecord> it = connections.iterator();
while (it.hasNext()) {
if (it.next().connection == conn) {
long now = clock.currentTimeMillis();
if (exception) connectionFailed(now);
else considerRaisingConnectionLimit(now);
it.remove();
break;
}
}
if (LOG.isLoggable(INFO))
LOG.info("Connection closed, " + connections.size() + " open");
}
}
@Override
public void allConnectionsClosed() {
public void bluetoothDisabled() {
synchronized (lock) {
LOG.info("Bluetooth disabled");
considerRaisingConnectionLimit(clock.currentTimeMillis());
connections.clear();
LOG.info("All connections closed");
}
}
@GuardedBy("lock")
private boolean isContactConnectionAllowedByLimit(long now) {
considerRaisingConnectionLimit(now);
if (connections.size() > connectionLimit) {
LOG.info("Refusing contact connection, above limit");
return false;
} else if (connections.size() < connectionLimit) {
LOG.info("Allowing contact connection, below limit");
return true;
} else if (now - timeOfLastAttempt >= attemptInterval) {
LOG.info("Allowing contact connection, at limit");
return true;
} else {
LOG.info("Refusing contact connection, at limit");
return false;
}
}
@GuardedBy("lock")
private void considerRaisingConnectionLimit(long now) {
int stable = 0;
for (ConnectionRecord rec : connections) {
if (now - rec.timeOpened >= STABILITY_PERIOD_MS) stable++;
}
if (stable > connectionLimit) {
LOG.info("Raising connection limit");
connectionLimit = stable;
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
}
if (LOG.isLoggable(INFO)) {
LOG.info(stable + " connections are stable, limit is "
+ connectionLimit);
}
}
@GuardedBy("lock")
private void connectionFailed(long now) {
if (connections.size() > connectionLimit &&
now - timeOfLastAttempt < STABILITY_PERIOD_MS) {
LOG.info("Connection failed above limit, increasing interval");
attemptInterval = min(attemptInterval * 2, MAX_ATTEMPT_INTERVAL_MS);
}
}
private static final class ConnectionRecord {
private final DuplexTransportConnection connection;
private final long timeOpened;
private ConnectionRecord(DuplexTransportConnection connection,
long timeOpened) {
this.connection = connection;
this.timeOpened = timeOpened;
}
}
}

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
@@ -60,12 +61,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
getLogger(BluetoothPlugin.class.getName());
final BluetoothConnectionLimiter connectionLimiter;
final TimeoutMonitor timeoutMonitor;
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final PluginCallback callback;
private final int maxLatency;
private final int maxLatency, maxIdleTime;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false, contactConnections = false;
@@ -105,14 +107,17 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
abstract DuplexTransportConnection discoverAndConnect(String uuid);
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, PluginCallback callback, int maxLatency) {
TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, Backoff backoff,
PluginCallback callback, int maxLatency, int maxIdleTime) {
this.connectionLimiter = connectionLimiter;
this.timeoutMonitor = timeoutMonitor;
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
}
void onAdapterEnabled() {
@@ -125,7 +130,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
tryToClose(socket);
connectionLimiter.allConnectionsClosed();
connectionLimiter.bluetoothDisabled();
callback.transportDisabled();
}
@@ -141,8 +146,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
return maxIdleTime;
}
@Override
@@ -227,13 +231,26 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
return;
}
backoff.reset();
if (connectionLimiter.contactConnectionOpened(conn))
LOG.info("Connection received");
if (connectionLimiter.contactConnectionOpened(conn, true)) {
backoff.reset();
callback.handleConnection(conn);
} else {
tryToClose(conn);
}
if (!running) return;
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getWriter().dispose(false);
conn.getReader().dispose(false, false);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
@Override
public void stop() {
running = false;
@@ -273,13 +290,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return;
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return;
if (!connectionLimiter.canOpenContactConnection()) return;
DuplexTransportConnection d = createConnection(p);
if (d != null) {
backoff.reset();
if (connectionLimiter.contactConnectionOpened(d))
h.handleConnection(d);
h.handleConnection(d);
}
});
}
@@ -325,8 +339,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (isNullOrEmpty(uuid)) return null;
DuplexTransportConnection conn = connect(address, uuid);
if (conn == null) return null;
// TODO: Why don't we reset the backoff here?
return connectionLimiter.contactConnectionOpened(conn) ? conn : null;
if (connectionLimiter.contactConnectionOpened(conn, false)) {
return conn;
} else {
tryToClose(conn);
return null;
}
}
@Override

View File

@@ -17,18 +17,20 @@ import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.lang.Integer.parseInt;
import static java.util.Collections.addAll;
import static java.util.Collections.emptyList;
import static java.util.Collections.sort;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@@ -37,6 +39,7 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TR
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_PORT;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -47,15 +50,36 @@ class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG = getLogger(LanTcpPlugin.class.getName());
private static final LanAddressComparator ADDRESS_COMPARATOR =
new LanAddressComparator();
private static final int MAX_ADDRESSES = 4;
private static final String SEPARATOR = ",";
/**
* The IP address of an Android device providing a wifi access point.
*/
protected static final InetAddress WIFI_AP_ADDRESS;
/**
* The IP address of an Android device providing a wifi direct
* legacy mode access point.
*/
protected static final InetAddress WIFI_DIRECT_AP_ADDRESS;
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(
new byte[] {(byte) 192, (byte) 168, 43, 1});
WIFI_DIRECT_AP_ADDRESS = InetAddress.getByAddress(
new byte[] {(byte) 192, (byte) 168, 49, 1});
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
LanTcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
int maxLatency, int maxIdleTime, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
}
@Override
@@ -64,37 +88,81 @@ class LanTcpPlugin extends TcpPlugin {
}
@Override
protected List<InetSocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available
public void start() {
if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty();
running = true;
bind();
}
protected void initialisePortProperty() {
TransportProperties p = callback.getLocalProperties();
if (isNullOrEmpty(p.get(PROP_PORT))) {
int port = new Random().nextInt(32768) + 32768;
p.put(PROP_PORT, String.valueOf(port));
callback.mergeLocalProperties(p);
}
}
@Override
protected List<InetSocketAddress> getLocalSocketAddresses() {
TransportProperties p = callback.getLocalProperties();
int preferredPort = parsePortProperty(p.get(PROP_PORT));
String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>();
for (InetAddress local : getLocalIpAddresses()) {
if (isAcceptableAddress(local)) {
// If this is the old address, try to use the same port
for (InetSocketAddress old : olds) {
if (old.getAddress().equals(local))
locals.add(new InetSocketAddress(local, old.getPort()));
List<InetSocketAddress> fallbacks = new ArrayList<>();
for (InetAddress local : getUsableLocalInetAddresses()) {
// If we've used this address before, try to use the same port
int port = preferredPort;
for (InetSocketAddress old : olds) {
if (old.getAddress().equals(local)) {
port = old.getPort();
break;
}
locals.add(new InetSocketAddress(local, 0));
}
locals.add(new InetSocketAddress(local, port));
// Fall back to any available port
fallbacks.add(new InetSocketAddress(local, 0));
}
sort(locals, ADDRESS_COMPARATOR);
locals.addAll(fallbacks);
return locals;
}
private int parsePortProperty(@Nullable String portProperty) {
if (isNullOrEmpty(portProperty)) return 0;
try {
return parseInt(portProperty);
} catch (NumberFormatException e) {
return 0;
}
}
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
if (isNullOrEmpty(ipPorts)) return emptyList();
String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> addresses = new ArrayList<>();
for (String ipPort : split) {
if (isNullOrEmpty(ipPorts)) return addresses;
for (String ipPort : ipPorts.split(SEPARATOR)) {
InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) addresses.add(a);
}
return addresses;
}
protected List<InetAddress> getUsableLocalInetAddresses() {
List<InterfaceAddress> ifAddrs =
new ArrayList<>(getLocalInterfaceAddresses());
// Prefer longer network prefixes
sort(ifAddrs, (a, b) ->
b.getNetworkPrefixLength() - a.getNetworkPrefixLength());
List<InetAddress> addrs = new ArrayList<>();
for (InterfaceAddress ifAddr : ifAddrs) {
InetAddress addr = ifAddr.getAddress();
if (isAcceptableAddress(addr)) addrs.add(addr);
}
return addrs;
}
@Override
protected void setLocalSocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a);
@@ -132,7 +200,20 @@ class LanTcpPlugin extends TcpPlugin {
@Override
protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p) {
return parseSocketAddresses(p.get(PROP_IP_PORTS));
String ipPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> remotes = parseSocketAddresses(ipPorts);
int port = parsePortProperty(p.get(PROP_PORT));
// If the contact has a preferred port, we can guess their IP:port when
// they're providing a wifi access point
if (port != 0) {
InetSocketAddress wifiAp =
new InetSocketAddress(WIFI_AP_ADDRESS, port);
if (!remotes.contains(wifiAp)) remotes.add(wifiAp);
InetSocketAddress wifiDirectAp =
new InetSocketAddress(WIFI_DIRECT_AP_ADDRESS, port);
if (!remotes.contains(wifiDirectAp)) remotes.add(wifiDirectAp);
}
return remotes;
}
private boolean isAcceptableAddress(InetAddress a) {
@@ -145,52 +226,33 @@ class LanTcpPlugin extends TcpPlugin {
}
@Override
protected boolean isConnectable(InetSocketAddress remote) {
protected boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote) {
if (remote.getPort() == 0) return false;
if (!isAcceptableAddress(remote.getAddress())) return false;
// Try to determine whether the address is on the same LAN as us
if (socket == null) return false;
byte[] localIp = socket.getInetAddress().getAddress();
byte[] localIp = local.getAddress().getAddress();
byte[] remoteIp = remote.getAddress().getAddress();
return addressesAreOnSameLan(localIp, remoteIp);
int prefixLength = local.getNetworkPrefixLength();
return areAddressesInSameNetwork(localIp, remoteIp, prefixLength);
}
// Package access for testing
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
// 10.0.0.0/8
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
// 172.16.0.0/12
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
// 192.168.0.0/16
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
// Unrecognised prefix - may be compatible
static boolean areAddressesInSameNetwork(byte[] localIp, byte[] remoteIp,
int prefixLength) {
if (localIp.length != remoteIp.length) return false;
// Compare the first prefixLength bits of the addresses
for (int i = 0; i < prefixLength; i++) {
int byteIndex = i >> 3;
int bitIndex = i & 7; // 0 to 7
int mask = 128 >> bitIndex; // Select the bit at bitIndex
if ((localIp[byteIndex] & mask) != (remoteIp[byteIndex] & mask)) {
return false; // Addresses differ at bit i
}
}
return true;
}
private static boolean isPrefix10(byte[] ipv4) {
return ipv4[0] == 10;
}
private static boolean isPrefix172(byte[] ipv4) {
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
}
private static boolean isPrefix192(byte[] ipv4) {
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
}
// Returns the prefix length for an RFC 1918 address, or 0 for any other
// address
private static int getRfc1918PrefixLength(InetAddress addr) {
if (!(addr instanceof Inet4Address)) return 0;
if (!addr.isSiteLocalAddress()) return 0;
byte[] ipv4 = addr.getAddress();
if (isPrefix10(ipv4)) return 8;
if (isPrefix172(ipv4)) return 12;
if (isPrefix192(ipv4)) return 16;
return 0;
}
@Override
public boolean supportsKeyAgreement() {
return true;
@@ -229,6 +291,12 @@ class LanTcpPlugin extends TcpPlugin {
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null;
ServerSocket ss = socket;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) {
LOG.warning("No interface for key agreement server socket");
return null;
}
InetSocketAddress remote;
try {
remote = parseSocketAddress(descriptor);
@@ -236,12 +304,11 @@ class LanTcpPlugin extends TcpPlugin {
LOG.info("Invalid IP/port in key agreement descriptor");
return null;
}
if (!isConnectable(remote)) {
if (!isConnectable(local, remote)) {
if (LOG.isLoggable(INFO)) {
SocketAddress local = socket.getLocalSocketAddress();
LOG.info(scrubSocketAddress(remote) +
" is not connectable from " +
scrubSocketAddress(local));
scrubSocketAddress(ss.getLocalSocketAddress()));
}
return null;
}
@@ -249,8 +316,8 @@ class LanTcpPlugin extends TcpPlugin {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.connect(remote);
s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -299,19 +366,4 @@ class LanTcpPlugin extends TcpPlugin {
IoUtils.tryToClose(ss, LOG, WARNING);
}
}
static class LanAddressComparator implements Comparator<InetSocketAddress> {
@Override
public int compare(InetSocketAddress a, InetSocketAddress b) {
// Prefer addresses with non-zero ports
int aPort = a.getPort(), bPort = b.getPort();
if (aPort > 0 && bPort == 0) return -1;
if (aPort == 0 && bPort > 0) return 1;
// Prefer addresses with longer RFC 1918 prefixes
int aPrefix = getRfc1918PrefixLength(a.getAddress());
int bPrefix = getRfc1918PrefixLength(b.getAddress());
return bPrefix - aPrefix;
}
}
}

View File

@@ -18,10 +18,11 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@NotNullByDefault
public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_LATENCY = 30_000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
@@ -48,6 +49,6 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY,
MAX_IDLE_TIME);
MAX_IDLE_TIME, CONNECTION_TIMEOUT);
}
}

View File

@@ -19,10 +19,10 @@ import org.briarproject.bramble.util.IoUtils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
@@ -36,7 +36,6 @@ import java.util.regex.Pattern;
import javax.annotation.Nullable;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.emptyList;
import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
@@ -58,7 +57,8 @@ abstract class TcpPlugin implements DuplexPlugin {
protected final Executor ioExecutor, bindExecutor;
protected final Backoff backoff;
protected final PluginCallback callback;
protected final int maxLatency, maxIdleTime, socketTimeout;
protected final int maxLatency, maxIdleTime;
protected final int connectionTimeout, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false);
protected volatile boolean running = false;
@@ -86,15 +86,18 @@ abstract class TcpPlugin implements DuplexPlugin {
/**
* Returns true if connections to the given address can be attempted.
*/
protected abstract boolean isConnectable(InetSocketAddress remote);
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected abstract boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote);
TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
int maxLatency, int maxIdleTime) {
int maxLatency, int maxIdleTime, int connectionTimeout) {
this.ioExecutor = ioExecutor;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.connectionTimeout = connectionTimeout;
if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
@@ -230,13 +233,23 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (!isRunning()) return null;
ServerSocket ss = socket;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) {
LOG.warning("No interface for server socket");
return null;
}
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) {
if (!isConnectable(remote)) {
// Don't try to connect to our own address
if (!canConnectToOwnAddress() &&
remote.getAddress().equals(ss.getInetAddress())) {
continue;
}
if (!isConnectable(local, remote)) {
if (LOG.isLoggable(INFO)) {
SocketAddress local = socket.getLocalSocketAddress();
LOG.info(scrubSocketAddress(remote) +
" is not connectable from " +
scrubSocketAddress(local));
scrubSocketAddress(ss.getLocalSocketAddress()));
}
continue;
}
@@ -244,8 +257,8 @@ abstract class TcpPlugin implements DuplexPlugin {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.connect(remote);
s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -259,6 +272,19 @@ abstract class TcpPlugin implements DuplexPlugin {
return null;
}
@Nullable
InterfaceAddress getLocalInterfaceAddress(InetAddress a) {
for (InterfaceAddress ifAddr : getLocalInterfaceAddresses()) {
if (ifAddr.getAddress().equals(a)) return ifAddr;
}
return null;
}
// Override for testing
protected boolean canConnectToOwnAddress() {
return false;
}
protected Socket createSocket() throws IOException {
return new Socket();
}
@@ -314,14 +340,27 @@ abstract class TcpPlugin implements DuplexPlugin {
throw new UnsupportedOperationException();
}
Collection<InetAddress> getLocalIpAddresses() {
List<InterfaceAddress> getLocalInterfaceAddresses() {
List<InterfaceAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : getNetworkInterfaces()) {
addrs.addAll(iface.getInterfaceAddresses());
}
return addrs;
}
List<InetAddress> getLocalInetAddresses() {
List<InetAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : getNetworkInterfaces()) {
addrs.addAll(list(iface.getInetAddresses()));
}
return addrs;
}
private List<NetworkInterface> getNetworkInterfaces() {
try {
Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
if (ifaces == null) return emptyList();
List<InetAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : list(ifaces))
addrs.addAll(list(iface.getInetAddresses()));
return addrs;
Enumeration<NetworkInterface> ifaces =
NetworkInterface.getNetworkInterfaces();
return ifaces == null ? emptyList() : list(ifaces);
} catch (SocketException e) {
logException(LOG, WARNING, e);
return emptyList();

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -29,8 +30,10 @@ class WanTcpPlugin extends TcpPlugin {
private volatile MappingResult mappingResult;
WanTcpPlugin(Executor ioExecutor, Backoff backoff, PortMapper portMapper,
PluginCallback callback, int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
PluginCallback callback, int maxLatency, int maxIdleTime,
int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
this.portMapper = portMapper;
}
@@ -45,7 +48,7 @@ class WanTcpPlugin extends TcpPlugin {
TransportProperties p = callback.getLocalProperties();
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT));
List<InetSocketAddress> addrs = new LinkedList<>();
for (InetAddress a : getLocalIpAddresses()) {
for (InetAddress a : getLocalInetAddresses()) {
if (isAcceptableAddress(a)) {
// If this is the old address, try to use the same port
if (old != null && old.getAddress().equals(a))
@@ -86,7 +89,8 @@ class WanTcpPlugin extends TcpPlugin {
}
@Override
protected boolean isConnectable(InetSocketAddress remote) {
protected boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote) {
if (remote.getPort() == 0) return false;
return isAcceptableAddress(remote.getAddress());
}

View File

@@ -19,10 +19,11 @@ import static org.briarproject.bramble.api.plugin.WanTcpConstants.ID;
@NotNullByDefault
public class WanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_LATENCY = 30_000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 30_000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
@@ -52,6 +53,6 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new WanTcpPlugin(ioExecutor, backoff,
new PortMapperImpl(shutdownManager), callback, MAX_LATENCY,
MAX_IDLE_TIME);
MAX_IDLE_TIME, CONNECTION_TIMEOUT);
}
}

View File

@@ -195,8 +195,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!assetsAreUpToDate()) installAssets();
if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted");
// Migrate old settings before having a chance to stop
migrateSettings();
// Start a new Tor process
LOG.info("Starting Tor");
String torPath = torFile.getAbsolutePath();
@@ -816,21 +814,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
}
// TODO remove when sufficient time has passed. Added 2018-08-15
private void migrateSettings() {
Settings sOld = callback.getSettings();
int oldNetwork = sOld.getInt("network", -1);
if (oldNetwork == -1) return;
Settings s = new Settings();
if (oldNetwork == 0) {
s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_NEVER);
} else if (oldNetwork == 1) {
s.putBoolean(PREF_TOR_MOBILE, false);
}
s.putInt("network", -1);
callback.mergeSettings(s);
}
private static class ConnectionStatus {
// All of the following are locking: this

View File

@@ -37,6 +37,11 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.GROUP_KEY_DISCOVERED;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
@Immutable
@NotNullByDefault
class TransportPropertyManagerImpl implements TransportPropertyManager,
@@ -111,10 +116,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
try {
// Find the latest update for this transport, if any
BdfDictionary d = metadataParser.parse(meta);
TransportId t = new TransportId(d.getString("transportId"));
TransportId t = new TransportId(d.getString(MSG_KEY_TRANSPORT_ID));
LatestUpdate latest = findLatest(txn, m.getGroupId(), t, false);
if (latest != null) {
if (d.getLong("version") > latest.version) {
if (d.getLong(MSG_KEY_VERSION) > latest.version) {
// This update is newer - delete the previous update
db.deleteMessage(txn, latest.messageId);
db.deleteMessageMetadata(txn, latest.messageId);
@@ -140,6 +145,27 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
}
}
@Override
public void addRemotePropertiesFromConnection(ContactId c, TransportId t,
TransportProperties props) throws DbException {
if (props.isEmpty()) return;
try {
db.transaction(false, txn -> {
Group g = getContactGroup(db.getContact(txn, c));
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(
txn, g.getId());
BdfDictionary discovered =
meta.getOptionalDictionary(GROUP_KEY_DISCOVERED);
if (discovered == null) discovered = new BdfDictionary();
discovered.putAll(props);
meta.put(GROUP_KEY_DISCOVERED, discovered);
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
});
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException {
@@ -203,12 +229,26 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
Group g = getContactGroup(c);
try {
// Find the latest remote update
TransportProperties remote;
LatestUpdate latest = findLatest(txn, g.getId(), t, false);
if (latest == null) return new TransportProperties();
// Retrieve and parse the latest remote properties
BdfList message =
clientHelper.getMessageAsList(txn, latest.messageId);
return parseProperties(message);
if (latest == null) {
remote = new TransportProperties();
} else {
// Retrieve and parse the latest remote properties
BdfList message =
clientHelper.getMessageAsList(txn, latest.messageId);
remote = parseProperties(message);
}
// Merge in any discovered properties
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g.getId());
BdfDictionary d = meta.getOptionalDictionary(GROUP_KEY_DISCOVERED);
if (d == null) return remote;
TransportProperties merged =
clientHelper.parseAndValidateTransportProperties(d);
// Received properties override discovered properties
merged.putAll(remote);
return merged;
} catch (FormatException e) {
throw new DbException(e);
}
@@ -281,10 +321,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
long now = clock.currentTimeMillis();
Message m = clientHelper.createMessage(g, now, body);
BdfDictionary meta = new BdfDictionary();
meta.put("transportId", t.getString());
meta.put("version", version);
meta.put("local", local);
clientHelper.addLocalMessage(txn, m, meta, shared);
meta.put(MSG_KEY_TRANSPORT_ID, t.getString());
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, local);
clientHelper.addLocalMessage(txn, m, meta, shared, false);
} catch (FormatException e) {
throw new RuntimeException(e);
}
@@ -302,8 +342,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
TransportId t = new TransportId(meta.getString("transportId"));
long version = meta.getLong("version");
TransportId t =
new TransportId(meta.getString(MSG_KEY_TRANSPORT_ID));
long version = meta.getLong(MSG_KEY_VERSION);
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
}
return latestUpdates;
@@ -316,9 +357,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getString("transportId").equals(t.getString())
&& meta.getBoolean("local") == local) {
return new LatestUpdate(e.getKey(), meta.getLong("version"));
if (meta.getString(MSG_KEY_TRANSPORT_ID).equals(t.getString())
&& meta.getBoolean(MSG_KEY_LOCAL) == local) {
return new LatestUpdate(e.getKey(),
meta.getLong(MSG_KEY_VERSION));
}
}
return null;

View File

@@ -66,6 +66,7 @@ import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNull;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
@@ -158,9 +159,7 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
private void addPendingContact(PendingContact p) {
long now = clock.currentTimeMillis();
long expiry = p.getTimestamp() + RENDEZVOUS_TIMEOUT_MS;
if (expiry > now) {
broadcastState(p.getId(), WAITING_FOR_CONNECTION);
} else {
if (expiry <= now) {
broadcastState(p.getId(), FAILED);
return;
}
@@ -180,9 +179,13 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
for (PluginState ps : pluginStates.values()) {
RendezvousEndpoint endpoint =
createEndpoint(ps.plugin, p.getId(), cs);
if (endpoint != null)
if (endpoint != null) {
requireNull(ps.endpoints.put(p.getId(), endpoint));
cs.numEndpoints++;
}
}
if (cs.numEndpoints == 0) broadcastState(p.getId(), OFFLINE);
else broadcastState(p.getId(), WAITING_FOR_CONNECTION);
} catch (DbException | GeneralSecurityException e) {
logException(LOG, WARNING, e);
}
@@ -328,9 +331,14 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
TransportId t = plugin.getId();
Map<PendingContactId, RendezvousEndpoint> endpoints = new HashMap<>();
for (Entry<PendingContactId, CryptoState> e : cryptoStates.entrySet()) {
RendezvousEndpoint endpoint =
createEndpoint(plugin, e.getKey(), e.getValue());
if (endpoint != null) endpoints.put(e.getKey(), endpoint);
PendingContactId p = e.getKey();
CryptoState cs = e.getValue();
RendezvousEndpoint endpoint = createEndpoint(plugin, p, cs);
if (endpoint != null) {
endpoints.put(p, endpoint);
if (++cs.numEndpoints == 1)
broadcastState(p, WAITING_FOR_CONNECTION);
}
}
requireNull(pluginStates.put(t, new PluginState(plugin, endpoints)));
}
@@ -344,8 +352,11 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
private void removeTransport(TransportId t) {
PluginState ps = pluginStates.remove(t);
if (ps != null) {
for (RendezvousEndpoint endpoint : ps.endpoints.values()) {
tryToClose(endpoint, LOG, INFO);
for (Entry<PendingContactId, RendezvousEndpoint> e :
ps.endpoints.entrySet()) {
tryToClose(e.getValue(), LOG, INFO);
CryptoState cs = cryptoStates.get(e.getKey());
if (--cs.numEndpoints == 0) broadcastState(e.getKey(), OFFLINE);
}
}
}
@@ -391,6 +402,8 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
private final boolean alice;
private final long expiry;
private int numEndpoints = 0;
private CryptoState(SecretKey rendezvousKey, boolean alice,
long expiry) {
this.rendezvousKey = rendezvousKey;

View File

@@ -11,12 +11,15 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
@@ -39,9 +42,11 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
import static org.briarproject.bramble.util.LogUtils.logException;
/**
@@ -55,7 +60,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class DuplexOutgoingSession implements SyncSession, EventListener {
private static final Logger LOG =
Logger.getLogger(DuplexOutgoingSession.class.getName());
getLogger(DuplexOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE = () -> {
};
@@ -68,6 +73,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final EventBus eventBus;
private final Clock clock;
private final ContactId contactId;
private final TransportId transportId;
private final int maxLatency, maxIdleTime;
private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter;
@@ -83,14 +89,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private volatile boolean interrupted = false;
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
int maxIdleTime, StreamWriter streamWriter,
SyncRecordWriter recordWriter) {
EventBus eventBus, Clock clock, ContactId contactId,
TransportId transportId, int maxLatency, int maxIdleTime,
StreamWriter streamWriter, SyncRecordWriter recordWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.clock = clock;
this.contactId = contactId;
this.transportId = transportId;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.streamWriter = streamWriter;
@@ -103,6 +110,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() throws IOException {
eventBus.addListener(this);
try {
// Send our supported protocol versions
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
// Start a query for each type of record
generateAck();
generateBatch();
@@ -218,6 +227,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof CloseSyncConnectionsEvent) {
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
if (c.getTransportId().equals(transportId)) interrupt();
}
}

View File

@@ -18,14 +18,17 @@ import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -37,7 +40,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class IncomingSession implements SyncSession, EventListener {
private static final Logger LOG =
Logger.getLogger(IncomingSession.class.getName());
getLogger(IncomingSession.class.getName());
private final DatabaseComponent db;
private final Executor dbExecutor;
@@ -80,6 +83,9 @@ class IncomingSession implements SyncSession, EventListener {
} else if (recordReader.hasRequest()) {
Request r = recordReader.readRequest();
dbExecutor.execute(new ReceiveRequest(r));
} else if (recordReader.hasVersions()) {
Versions v = recordReader.readVersions();
dbExecutor.execute(new ReceiveVersions(v));
} else {
// unknown records are ignored in RecordReader#eof()
throw new FormatException();
@@ -190,4 +196,26 @@ class IncomingSession implements SyncSession, EventListener {
}
}
}
private class ReceiveVersions implements Runnable {
private final Versions versions;
private ReceiveVersions(Versions versions) {
this.versions = versions;
}
@DatabaseExecutor
@Override
public void run() {
try {
List<Byte> supported = versions.getSupportedVersions();
db.transaction(false,
txn -> db.setSyncVersions(txn, contactId, supported));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
}
}
}
}

View File

@@ -11,10 +11,13 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException;
@@ -29,9 +32,11 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
import static org.briarproject.bramble.util.LogUtils.logException;
/**
@@ -44,7 +49,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class SimplexOutgoingSession implements SyncSession, EventListener {
private static final Logger LOG =
Logger.getLogger(SimplexOutgoingSession.class.getName());
getLogger(SimplexOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE = () -> {
};
@@ -53,6 +58,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final Executor dbExecutor;
private final EventBus eventBus;
private final ContactId contactId;
private final TransportId transportId;
private final int maxLatency;
private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter;
@@ -62,12 +68,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private volatile boolean interrupted = false;
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, int maxLatency,
StreamWriter streamWriter, SyncRecordWriter recordWriter) {
EventBus eventBus, ContactId contactId, TransportId transportId,
int maxLatency, StreamWriter streamWriter,
SyncRecordWriter recordWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.contactId = contactId;
this.transportId = transportId;
this.maxLatency = maxLatency;
this.streamWriter = streamWriter;
this.recordWriter = recordWriter;
@@ -80,6 +88,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
public void run() throws IOException {
eventBus.addListener(this);
try {
// Send our supported protocol versions
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
// Start a query for each type of record
dbExecutor.execute(new GenerateAck());
dbExecutor.execute(new GenerateBatch());
@@ -118,6 +128,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof CloseSyncConnectionsEvent) {
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
if (c.getTransportId().equals(transportId)) interrupt();
}
}

View File

@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.util.ByteUtils;
import java.io.IOException;
@@ -26,6 +27,8 @@ import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@@ -45,7 +48,7 @@ class SyncRecordReaderImpl implements SyncRecordReader {
private static boolean isKnownRecordType(byte type) {
return type == ACK || type == MESSAGE || type == OFFER ||
type == REQUEST;
type == REQUEST || type == VERSIONS;
}
private final MessageFactory messageFactory;
@@ -148,4 +151,27 @@ class SyncRecordReaderImpl implements SyncRecordReader {
if (!hasRequest()) throw new FormatException();
return new Request(readMessageIds());
}
@Override
public boolean hasVersions() throws IOException {
return !eof() && getNextRecordType() == VERSIONS;
}
@Override
public Versions readVersions() throws IOException {
if (!hasVersions()) throw new FormatException();
return new Versions(readSupportedVersions());
}
private List<Byte> readSupportedVersions() throws IOException {
if (nextRecord == null) throw new AssertionError();
byte[] payload = nextRecord.getPayload();
if (payload.length == 0) throw new FormatException();
if (payload.length > MAX_SUPPORTED_VERSIONS)
throw new FormatException();
List<Byte> supported = new ArrayList<>(payload.length);
for (byte b : payload) supported.add(b);
nextRecord = null;
return supported;
}
}

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.Versions;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -20,6 +21,7 @@ import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@NotThreadSafe
@@ -65,6 +67,12 @@ class SyncRecordWriterImpl implements SyncRecordWriter {
writeRecord(REQUEST);
}
@Override
public void writeVersions(Versions v) throws IOException {
for (byte b : v.getSupportedVersions()) payload.write(b);
writeRecord(VERSIONS);
}
@Override
public void flush() throws IOException {
writer.flush();

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
@@ -53,22 +54,23 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
}
@Override
public SyncSession createSimplexOutgoingSession(ContactId c,
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
maxLatency, streamWriter, recordWriter);
}
@Override
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, StreamWriter streamWriter) {
public SyncSession createDuplexOutgoingSession(ContactId c,
TransportId t, int maxLatency, int maxIdleTime,
StreamWriter streamWriter) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, t,
maxLatency, maxIdleTime, streamWriter, recordWriter);
}
}

View File

@@ -314,6 +314,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
try {
shareMsg = hook.incomingMessage(txn, m, meta);
} catch (InvalidMessageException e) {
logException(LOG, INFO, e);
invalidateMessage(txn, m.getId());
return new DeliveryResult(false, false);
}

View File

@@ -26,7 +26,9 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
import org.briarproject.bramble.api.versioning.ClientVersion;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.event.ClientVersionUpdatedEvent;
import java.util.ArrayList;
import java.util.Collection;
@@ -80,9 +82,9 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
@Override
public void registerClient(ClientId clientId, int majorVersion,
int minorVersion, ClientVersioningHook hook) {
ClientMajorVersion cv = new ClientMajorVersion(clientId, majorVersion);
clients.add(new ClientVersion(cv, minorVersion));
hooks.put(cv, hook);
ClientMajorVersion cmv = new ClientMajorVersion(clientId, majorVersion);
clients.add(new ClientVersion(cmv, minorVersion));
hooks.put(cmv, hook);
}
@Override
@@ -96,9 +98,9 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
Map<ClientMajorVersion, Visibility> visibilities =
getVisibilities(localUpdate.states, remoteUpdate.states);
ClientMajorVersion cv =
ClientMajorVersion key =
new ClientMajorVersion(clientId, majorVersion);
Visibility v = visibilities.get(cv);
Visibility v = visibilities.get(key);
return v == null ? INVISIBLE : v;
} catch (FormatException e) {
throw new DbException(e);
@@ -112,10 +114,11 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
LatestUpdates latest = findLatestUpdates(txn, contactId);
if (latest == null || latest.remote == null) return -1;
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
ClientMajorVersion cv =
ClientMajorVersion key =
new ClientMajorVersion(clientId, majorVersion);
for (ClientState remote : remoteUpdate.states) {
if (remote.majorVersion.equals(cv)) return remote.minorVersion;
if (remote.clientVersion.getClientMajorVersion().equals(key))
return remote.clientVersion.getMinorVersion();
}
return -1;
} catch (FormatException e) {
@@ -226,9 +229,21 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates);
// Call hooks for any visibilities that have changed
ContactId c = getContactId(txn, m.getGroupId());
if (!before.equals(after)) {
Contact c = getContact(txn, m.getGroupId());
callVisibilityHooks(txn, c, before, after);
Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after);
}
// Broadcast events for any new client versions
Set<ClientVersion> oldRemoteVersions = new HashSet<>();
for (ClientState cs : oldRemoteStates) {
oldRemoteVersions.add(cs.clientVersion);
}
for (ClientState cs : newRemoteStates) {
if (!oldRemoteVersions.contains(cs.clientVersion)) {
txn.attach(new ClientVersionUpdatedEvent(c,
cs.clientVersion));
}
}
} catch (FormatException e) {
throw new InvalidMessageException(e);
@@ -243,7 +258,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
try {
Message m = clientHelper.createMessage(localGroup.getId(), now,
body);
db.addLocalMessage(txn, m, new Metadata(), false);
db.addLocalMessage(txn, m, new Metadata(), false, false);
} catch (FormatException e) {
throw new AssertionError(e);
}
@@ -256,8 +271,8 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
}
private BdfList encodeClientVersion(ClientVersion cv) {
return BdfList.of(cv.majorVersion.getClientId().getString(),
cv.majorVersion.getMajorVersion(), cv.minorVersion);
return BdfList.of(cv.getClientId().getString(), cv.getMajorVersion(),
cv.getMinorVersion());
}
/**
@@ -298,8 +313,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
ClientId clientId = new ClientId(cv.getString(0));
int majorVersion = cv.getLong(1).intValue();
int minorVersion = cv.getLong(2).intValue();
parsed.add(new ClientVersion(clientId, majorVersion,
minorVersion));
parsed.add(new ClientVersion(clientId, majorVersion, minorVersion));
}
return parsed;
}
@@ -418,13 +432,15 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
private List<ClientState> updateStatesFromLocalVersions(
List<ClientState> oldStates, List<ClientVersion> newVersions) {
Map<ClientMajorVersion, ClientState> oldMap = new HashMap<>();
for (ClientState cs : oldStates) oldMap.put(cs.majorVersion, cs);
for (ClientState cs : oldStates) {
oldMap.put(cs.clientVersion.getClientMajorVersion(), cs);
}
List<ClientState> newStates = new ArrayList<>(newVersions.size());
for (ClientVersion newVersion : newVersions) {
ClientState oldState = oldMap.get(newVersion.majorVersion);
ClientMajorVersion key = newVersion.getClientMajorVersion();
ClientState oldState = oldMap.get(key);
boolean active = oldState != null && oldState.active;
newStates.add(new ClientState(newVersion.majorVersion,
newVersion.minorVersion, active));
newStates.add(new ClientState(newVersion, active));
}
return newStates;
}
@@ -438,7 +454,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true);
clientHelper.addLocalMessage(txn, m, meta, true, false);
} catch (FormatException e) {
throw new RuntimeException(e);
}
@@ -451,21 +467,25 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
}
private BdfList encodeClientState(ClientState cs) {
return BdfList.of(cs.majorVersion.getClientId().getString(),
cs.majorVersion.getMajorVersion(), cs.minorVersion, cs.active);
ClientVersion cv = cs.clientVersion;
return BdfList.of(cv.getClientId().getString(), cv.getMajorVersion(),
cv.getMinorVersion(), cs.active);
}
private Map<ClientMajorVersion, Visibility> getVisibilities(
List<ClientState> localStates, List<ClientState> remoteStates) {
Map<ClientMajorVersion, ClientState> remoteMap = new HashMap<>();
for (ClientState cs : remoteStates) remoteMap.put(cs.majorVersion, cs);
for (ClientState cs : remoteStates) {
remoteMap.put(cs.clientVersion.getClientMajorVersion(), cs);
}
Map<ClientMajorVersion, Visibility> visibilities = new HashMap<>();
for (ClientState local : localStates) {
ClientState remote = remoteMap.get(local.majorVersion);
if (remote == null) visibilities.put(local.majorVersion, INVISIBLE);
else if (remote.active)
visibilities.put(local.majorVersion, SHARED);
else visibilities.put(local.majorVersion, VISIBLE);
ClientMajorVersion key =
local.clientVersion.getClientMajorVersion();
ClientState remote = remoteMap.get(key);
if (remote == null) visibilities.put(key, INVISIBLE);
else if (remote.active) visibilities.put(key, SHARED);
else visibilities.put(key, VISIBLE);
}
return visibilities;
}
@@ -486,9 +506,9 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
}
}
private void callVisibilityHook(Transaction txn, ClientMajorVersion cv,
private void callVisibilityHook(Transaction txn, ClientMajorVersion cmv,
Contact c, Visibility v) throws DbException {
ClientVersioningHook hook = hooks.get(cv);
ClientVersioningHook hook = hooks.get(cmv);
if (hook != null) hook.onClientVisibilityChanging(txn, c, v);
}
@@ -496,18 +516,17 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
List<ClientVersion> versions) throws DbException {
List<ClientState> states = new ArrayList<>(versions.size());
for (ClientVersion cv : versions) {
states.add(new ClientState(cv.majorVersion, cv.minorVersion,
false));
states.add(new ClientState(cv, false));
}
storeUpdate(txn, g, states, 1);
}
private Contact getContact(Transaction txn, GroupId g) throws DbException {
private ContactId getContactId(Transaction txn, GroupId g)
throws DbException {
try {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g);
int id = meta.getLong(GROUP_KEY_CONTACT_ID).intValue();
return db.getContact(txn, new ContactId(id));
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e);
}
@@ -516,13 +535,16 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
private List<ClientState> updateStatesFromRemoteStates(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
Set<ClientMajorVersion> remoteSet = new HashSet<>();
for (ClientState cs : remoteStates) remoteSet.add(cs.majorVersion);
for (ClientState cs : remoteStates) {
remoteSet.add(cs.clientVersion.getClientMajorVersion());
}
List<ClientState> newLocalStates =
new ArrayList<>(oldLocalStates.size());
for (ClientState oldState : oldLocalStates) {
boolean active = remoteSet.contains(oldState.majorVersion);
newLocalStates.add(new ClientState(oldState.majorVersion,
oldState.minorVersion, active));
ClientMajorVersion cmv =
oldState.clientVersion.getClientMajorVersion();
boolean active = remoteSet.contains(cmv);
newLocalStates.add(new ClientState(oldState.clientVersion, active));
}
return newLocalStates;
}
@@ -561,61 +583,19 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
}
}
private static class ClientVersion implements Comparable<ClientVersion> {
private final ClientMajorVersion majorVersion;
private final int minorVersion;
private ClientVersion(ClientMajorVersion majorVersion,
int minorVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
}
private ClientVersion(ClientId clientId, int majorVersion,
int minorVersion) {
this(new ClientMajorVersion(clientId, majorVersion), minorVersion);
}
@Override
public boolean equals(Object o) {
if (o instanceof ClientVersion) {
ClientVersion cv = (ClientVersion) o;
return majorVersion.equals(cv.majorVersion)
&& minorVersion == cv.minorVersion;
}
return false;
}
@Override
public int hashCode() {
return majorVersion.hashCode();
}
@Override
public int compareTo(ClientVersion cv) {
int compare = majorVersion.compareTo(cv.majorVersion);
if (compare != 0) return compare;
return minorVersion - cv.minorVersion;
}
}
private static class ClientState {
private final ClientMajorVersion majorVersion;
private final int minorVersion;
private final ClientVersion clientVersion;
private final boolean active;
private ClientState(ClientMajorVersion majorVersion, int minorVersion,
boolean active) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
private ClientState(ClientVersion clientVersion, boolean active) {
this.clientVersion = clientVersion;
this.active = active;
}
private ClientState(ClientId clientId, int majorVersion,
int minorVersion, boolean active) {
this(new ClientMajorVersion(clientId, majorVersion), minorVersion,
this(new ClientVersion(clientId, majorVersion, minorVersion),
active);
}
@@ -623,8 +603,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
public boolean equals(Object o) {
if (o instanceof ClientState) {
ClientState cs = (ClientState) o;
return majorVersion.equals(cs.majorVersion)
&& minorVersion == cs.minorVersion
return clientVersion.equals(cs.clientVersion)
&& active == cs.active;
}
return false;
@@ -632,7 +611,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
@Override
public int hashCode() {
return majorVersion.hashCode();
return clientVersion.hashCode();
}
}
}

View File

@@ -1,6 +1,8 @@
package org.briarproject.bramble.account;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.Identity;
@@ -18,12 +20,15 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.annotation.Nullable;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -34,11 +39,14 @@ import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class AccountManagerImplTest extends BrambleMockTestCase {
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final KeyStrengthener keyStrengthener =
context.mock(KeyStrengthener.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
@@ -68,6 +76,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
will(returnValue(dbDir));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
allowing(databaseConfig).getKeyStrengthener();
will(returnValue(keyStrengthener));
}});
accountManager =
@@ -78,8 +88,13 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
}
@Test
public void testSignInReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.signIn(password));
public void testSignInThrowsExceptionIfDbKeyCannotBeLoaded() {
try {
accountManager.signIn(password);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_CIPHERTEXT, expected.getDecryptionResult());
}
assertFalse(accountManager.hasDatabaseKey());
assertFalse(keyFile.exists());
@@ -87,10 +102,11 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
}
@Test
public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception {
public void testSignInThrowsExceptionIfPasswordIsWrong() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(null));
oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener);
will(throwException(new DecryptionException(INVALID_PASSWORD)));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
@@ -99,7 +115,12 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertFalse(accountManager.signIn(password));
try {
accountManager.signIn(password);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_PASSWORD, expected.getDecryptionResult());
}
assertFalse(accountManager.hasDatabaseKey());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
@@ -109,8 +130,11 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Test
public void testSignInReturnsTrueIfPasswordIsRight() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener);
will(returnValue(key.getBytes()));
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
will(returnValue(true));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
@@ -119,7 +143,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(accountManager.signIn(password));
accountManager.signIn(password);
assertTrue(accountManager.hasDatabaseKey());
SecretKey decrypted = accountManager.getDatabaseKey();
assertNotNull(decrypted);
@@ -129,6 +153,35 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testSignInReEncryptsKey() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener);
will(returnValue(key.getBytes()));
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
will(returnValue(false));
oneOf(crypto).encryptWithPassword(key.getBytes(), password,
keyStrengthener);
will(returnValue(newEncryptedKey));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
accountManager.signIn(password);
assertTrue(accountManager.hasDatabaseKey());
SecretKey decrypted = accountManager.getDatabaseKey();
assertNotNull(decrypted);
assertArrayEquals(key.getBytes(), decrypted.getBytes());
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
@@ -201,55 +254,6 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
assertFalse(keyBackupFile.exists());
}
@Test
public void testAccountExistsReturnsFalseIfDbDirectoryDoesNotExist()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(dbDir.exists());
assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertFalse(dbDir.exists());
}
@Test
public void testAccountExistsReturnsFalseIfDbDirectoryIsNotDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.createNewFile());
assertFalse(dbDir.isDirectory());
assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertFalse(dbDir.isDirectory());
}
@Test
public void testAccountExistsReturnsTrueIfDbDirectoryIsDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.mkdirs());
assertTrue(dbDir.isDirectory());
assertTrue(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertTrue(dbDir.isDirectory());
}
@Test
public void testCreateAccountStoresDbKey() throws Exception {
context.checking(new Expectations() {{
@@ -258,7 +262,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
oneOf(identityManager).registerIdentity(identity);
oneOf(crypto).generateSecretKey();
will(returnValue(key));
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
oneOf(crypto).encryptWithPassword(key.getBytes(), password,
keyStrengthener);
will(returnValue(encryptedKey));
}});
@@ -276,25 +281,36 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
}
@Test
public void testChangePasswordReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.changePassword(password, newPassword));
public void testChangePasswordThrowsExceptionIfDbKeyCannotBeLoaded() {
try {
accountManager.changePassword(password, newPassword);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_CIPHERTEXT, expected.getDecryptionResult());
}
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testChangePasswordReturnsFalseIfPasswordIsWrong()
public void testChangePasswordThrowsExceptionIfPasswordIsWrong()
throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(null));
oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener);
will(throwException(new DecryptionException(INVALID_PASSWORD)));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(accountManager.changePassword(password, newPassword));
try {
accountManager.changePassword(password, newPassword);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_PASSWORD, expected.getDecryptionResult());
}
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
@@ -304,16 +320,20 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
public void testChangePasswordReturnsTrueIfPasswordIsRight()
throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener);
will(returnValue(key.getBytes()));
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword);
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
will(returnValue(true));
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword,
keyStrengthener);
will(returnValue(newEncryptedKey));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(accountManager.changePassword(password, newPassword));
accountManager.changePassword(password, newPassword);
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
@@ -322,7 +342,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes("UTF-8"));
out.write(hex.getBytes(Charset.forName("UTF-8")));
out.flush();
out.close();
}
@@ -330,7 +350,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Nullable
private String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
new FileInputStream(f), Charset.forName("UTF-8")));
String hex = reader.readLine();
reader.close();
return hex;

View File

@@ -96,7 +96,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(metadataEncoder).encode(dictionary);
will(returnValue(metadata));
oneOf(db).addLocalMessage(txn, message, metadata, shared);
oneOf(db).addLocalMessage(txn, message, metadata, shared, false);
}});
clientHelper.addLocalMessage(message, dictionary, shared);

View File

@@ -1,16 +1,21 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfigModule;
import org.briarproject.bramble.test.TestDuplexTransportConnection;
@@ -27,7 +32,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.fail;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair;
import static org.briarproject.bramble.test.TestPluginConfigModule.DUPLEX_TRANSPORT_ID;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
@@ -57,11 +62,11 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
alice = DaggerContactExchangeIntegrationTestComponent.builder()
.testDatabaseConfigModule(
new TestDatabaseConfigModule(aliceDir)).build();
alice.injectBrambleCoreEagerSingletons();
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(alice);
bob = DaggerContactExchangeIntegrationTestComponent.builder()
.testDatabaseConfigModule(new TestDatabaseConfigModule(bobDir))
.build();
bob.injectBrambleCoreEagerSingletons();
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(bob);
// Set up the devices and get the identities
aliceIdentity = setUp(alice, "Alice");
bobIdentity = setUp(bob, "Bob");
@@ -188,9 +193,14 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
private PendingContact addPendingContact(
ContactExchangeIntegrationTestComponent local,
ContactExchangeIntegrationTestComponent remote) throws Exception {
EventWaiter waiter = new EventWaiter();
local.getEventBus().addListener(waiter);
String link = remote.getContactManager().getHandshakeLink();
String alias = remote.getIdentityManager().getLocalAuthor().getName();
return local.getContactManager().addPendingContact(link, alias);
PendingContact pendingContact =
local.getContactManager().addPendingContact(link, alias);
waiter.latch.await(TIMEOUT, MILLISECONDS);
return pendingContact;
}
private void assertContacts(boolean verified,
@@ -237,7 +247,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
assertEquals(1, pairs.size());
Pair<PendingContact, PendingContactState> pair =
pairs.iterator().next();
assertEquals(WAITING_FOR_CONNECTION, pair.getSecond());
assertEquals(OFFLINE, pair.getSecond());
PendingContact pendingContact = pair.getFirst();
assertEquals(expectedIdentity.getLocalAuthor().getName(),
pendingContact.getAlias());
@@ -261,4 +271,19 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
tearDown(bob);
deleteTestDirectory(testDir);
}
@NotNullByDefault
private static class EventWaiter implements EventListener {
private final CountDownLatch latch = new CountDownLatch(1);
@Override
public void eventOccurred(Event e) {
if (e instanceof PendingContactStateChangedEvent) {
PendingContactStateChangedEvent p =
(PendingContactStateChangedEvent) e;
if (p.getPendingContactState() == OFFLINE) latch.countDown();
}
}
}
}

View File

@@ -1,40 +1,116 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.Random;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class PasswordBasedEncryptionTest extends BrambleTestCase {
public class PasswordBasedEncryptionTest extends BrambleMockTestCase {
private final KeyStrengthener keyStrengthener =
context.mock(KeyStrengthener.class);
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(),
new ScryptKdf(new SystemClock()));
@Test
public void testEncryptionAndDecryption() {
byte[] input = TestUtils.getRandomBytes(1234);
public void testEncryptionAndDecryption() throws Exception {
byte[] input = getRandomBytes(1234);
String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password);
byte[] output = crypto.decryptWithPassword(ciphertext, password);
byte[] ciphertext = crypto.encryptWithPassword(input, password, null);
byte[] output = crypto.decryptWithPassword(ciphertext, password, null);
assertArrayEquals(input, output);
}
@Test
public void testInvalidCiphertextReturnsNull() {
byte[] input = TestUtils.getRandomBytes(1234);
public void testInvalidFormatVersionThrowsException() {
byte[] input = getRandomBytes(1234);
String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password);
// Modify the ciphertext
int position = new Random().nextInt(ciphertext.length);
ciphertext[position] = (byte) (ciphertext[position] ^ 0xFF);
byte[] output = crypto.decryptWithPassword(ciphertext, password);
assertNull(output);
byte[] ciphertext = crypto.encryptWithPassword(input, password, null);
// Modify the format version
ciphertext[0] ^= (byte) 0xFF;
try {
crypto.decryptWithPassword(ciphertext, password, null);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_CIPHERTEXT, expected.getDecryptionResult());
}
}
@Test
public void testInvalidPasswordThrowsException() {
byte[] input = getRandomBytes(1234);
byte[] ciphertext = crypto.encryptWithPassword(input, "password", null);
// Try to decrypt with the wrong password
try {
crypto.decryptWithPassword(ciphertext, "wrong", null);
fail();
} catch (DecryptionException expected) {
assertEquals(INVALID_PASSWORD, expected.getDecryptionResult());
}
}
@Test
public void testMissingKeyStrengthenerThrowsException() {
SecretKey strengthened = getSecretKey();
context.checking(new Expectations() {{
oneOf(keyStrengthener).strengthenKey(with(any(SecretKey.class)));
will(returnValue(strengthened));
}});
// Use the key strengthener during encryption
byte[] input = getRandomBytes(1234);
String password = "password";
byte[] ciphertext =
crypto.encryptWithPassword(input, password, keyStrengthener);
// The key strengthener is missing during decryption
try {
crypto.decryptWithPassword(ciphertext, password, null);
fail();
} catch (DecryptionException expected) {
assertEquals(KEY_STRENGTHENER_ERROR, expected.getDecryptionResult());
}
}
@Test
public void testKeyStrengthenerFailureThrowsException() {
SecretKey strengthened = getSecretKey();
context.checking(new Expectations() {{
oneOf(keyStrengthener).strengthenKey(with(any(SecretKey.class)));
will(returnValue(strengthened));
oneOf(keyStrengthener).isInitialised();
will(returnValue(false));
}});
// Use the key strengthener during encryption
byte[] input = getRandomBytes(1234);
String password = "password";
byte[] ciphertext =
crypto.encryptWithPassword(input, password, keyStrengthener);
// The key strengthener fails during decryption
try {
crypto.decryptWithPassword(ciphertext, password, keyStrengthener);
fail();
} catch (DecryptionException expected) {
assertEquals(KEY_STRENGTHENER_ERROR, expected.getDecryptionResult());
}
}
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.ArrayClock;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
@@ -11,8 +12,6 @@ import java.util.HashSet;
import java.util.Set;
import static junit.framework.TestCase.assertTrue;
import org.briarproject.bramble.test.ArrayClock;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;

View File

@@ -61,10 +61,12 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -120,6 +122,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final Contact contact;
private final KeySetId keySetId;
private final PendingContactId pendingContactId;
private final Random random = new Random();
private final boolean shared = random.nextBoolean();
private final boolean temporary = random.nextBoolean();
public DatabaseComponentImplTest() {
clientId = getClientId();
@@ -253,7 +258,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
eventExecutor, shutdownManager);
db.transaction(false, transaction ->
db.addLocalMessage(transaction, message, metadata, true));
db.addLocalMessage(transaction, message, metadata, shared,
temporary));
}
@Test
@@ -265,20 +271,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).addMessage(txn, message, DELIVERED, shared,
temporary, null);
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).commitTransaction(txn);
// The message was added, so the listeners should be called
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
oneOf(eventBus)
.broadcast(with(any(MessageStateChangedEvent.class)));
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
oneOf(eventBus).broadcast(with(any(
MessageStateChangedEvent.class)));
if (shared)
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
db.transaction(false, transaction ->
db.addLocalMessage(transaction, message, metadata, true));
db.addLocalMessage(transaction, message, metadata, shared,
temporary));
}
@Test
@@ -286,11 +295,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not)
exactly(16).of(database).startTransaction();
exactly(18).of(database).startTransaction();
will(returnValue(txn));
exactly(16).of(database).containsContact(txn, contactId);
exactly(18).of(database).containsContact(txn, contactId);
will(returnValue(false));
exactly(16).of(database).abortTransaction(txn);
exactly(18).of(database).abortTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
@@ -368,6 +377,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected
}
try {
db.transaction(false, transaction ->
db.getSyncVersions(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
Ack a = new Ack(singletonList(messageId));
db.transaction(false, transaction ->
@@ -427,6 +444,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.setSyncVersions(transaction, contactId, emptyList()));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
}
@Test
@@ -569,11 +594,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception {
context.checking(new Expectations() {{
// Check whether the message is in the DB (which it's not)
exactly(11).of(database).startTransaction();
exactly(12).of(database).startTransaction();
will(returnValue(txn));
exactly(11).of(database).containsMessage(txn, messageId);
exactly(12).of(database).containsMessage(txn, messageId);
will(returnValue(false));
exactly(11).of(database).abortTransaction(txn);
exactly(12).of(database).abortTransaction(txn);
// Allow other checks to pass
allowing(database).containsContact(txn, contactId);
will(returnValue(true));
@@ -637,6 +662,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected
}
try {
db.transaction(false, transaction ->
db.setMessagePermanent(transaction, message.getId()));
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.setMessageShared(transaction, message.getId()));
@@ -972,7 +1005,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(VISIBLE));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
oneOf(database).addMessage(txn, message, UNKNOWN, false, false,
contactId);
// Second time
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
@@ -1507,6 +1541,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
public void testMessageDependencies() throws Exception {
int shutdownHandle = 12345;
MessageId messageId2 = new MessageId(getRandomId());
context.checking(new Expectations() {{
// open()
oneOf(database).open(key, null);
@@ -1521,7 +1556,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).addMessage(txn, message, DELIVERED, shared,
temporary, null);
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
// addMessageDependencies()
oneOf(database).containsMessage(txn, messageId);
@@ -1544,7 +1580,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
oneOf(eventBus).broadcast(with(any(
MessageStateChangedEvent.class)));
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
if (shared)
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
// endTransaction()
oneOf(database).commitTransaction(txn);
// close()
@@ -1555,7 +1592,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
assertFalse(db.open(key, null));
db.transaction(false, transaction -> {
db.addLocalMessage(transaction, message, metadata, true);
db.addLocalMessage(transaction, message, metadata, shared,
temporary);
Collection<MessageId> dependencies = new ArrayList<>(2);
dependencies.add(messageId1);
dependencies.add(messageId2);

View File

@@ -567,8 +567,9 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
MessageState state =
MessageState.fromValue(random.nextInt(4));
boolean shared = random.nextBoolean();
boolean temporary = random.nextBoolean();
ContactId sender = random.nextBoolean() ? c : null;
db.addMessage(txn, m, state, shared, sender);
db.addMessage(txn, m, state, shared, temporary, sender);
if (random.nextBoolean())
db.raiseRequestedFlag(txn, c, m.getId());
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
@@ -597,7 +598,8 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
for (int j = 0; j < MESSAGES_PER_GROUP; j++) {
Message m = getMessage(g.getId());
messages.add(m);
db.addMessage(txn, m, DELIVERED, false, null);
boolean temporary = random.nextBoolean();
db.addMessage(txn, m, DELIVERED, false, temporary, null);
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
messageMeta.get(g.getId()).add(mm);
db.mergeMessageMetadata(txn, m.getId(), mm);

View File

@@ -34,6 +34,7 @@ import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.SettableClock;
import org.briarproject.bramble.test.TestDatabaseConfig;
import org.briarproject.bramble.test.TestMessageFactory;
import org.briarproject.bramble.test.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -41,7 +42,6 @@ import org.junit.Test;
import java.io.File;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -51,6 +51,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
@@ -153,7 +154,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addGroup(txn, group);
assertTrue(db.containsGroup(txn, groupId));
assertFalse(db.containsMessage(txn, messageId));
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
assertTrue(db.containsMessage(txn, messageId));
db.commitTransaction(txn);
db.close();
@@ -191,7 +192,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and a message
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Removing the group should remove the message
assertTrue(db.containsMessage(txn, messageId));
@@ -213,7 +214,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// The contact has not seen the message, so it should be sendable
Collection<MessageId> ids =
@@ -244,7 +245,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, UNKNOWN, true, null);
db.addMessage(txn, message, UNKNOWN, true, false, null);
// The message has not been validated, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -288,7 +289,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// The group is invisible, so the message should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -340,7 +341,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, false, null);
db.addMessage(txn, message, DELIVERED, false, false, null);
// The message is not shared, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -371,7 +372,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// The message is sendable, but too large to send
Collection<MessageId> ids =
@@ -402,15 +403,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add some messages to ack
Message message1 = getMessage(groupId);
MessageId messageId1 = message1.getId();
db.addMessage(txn, message, DELIVERED, true, contactId);
db.addMessage(txn, message1, DELIVERED, true, contactId);
db.addMessage(txn, message, DELIVERED, true, false, contactId);
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
// Both message IDs should be returned
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(Arrays.asList(messageId, messageId1), ids);
assertEquals(asList(messageId, messageId1), ids);
// Remove both message IDs
db.lowerAckFlag(txn, contactId, Arrays.asList(messageId, messageId1));
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
// Both message IDs should have been removed
assertEquals(emptyList(), db.getMessagesToAck(txn,
@@ -422,7 +423,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Both message IDs should be returned
ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(Arrays.asList(messageId, messageId1), ids);
assertEquals(asList(messageId, messageId1), ids);
db.commitTransaction(txn);
db.close();
@@ -439,7 +440,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Retrieve the message from the database and mark it as sent
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -608,7 +609,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// The group is not visible so the message should not be visible
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
@@ -1149,6 +1150,43 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testGetContactsByHandshakePublicKey() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add an identity for a local author - no contacts should be
// associated
db.addIdentity(txn, identity);
PublicKey handshakePublicKey = TestUtils.getSignaturePublicKey();
Contact contact =
db.getContact(txn, handshakePublicKey, localAuthor.getId());
assertNull(contact);
// Add a contact associated with the local author
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
handshakePublicKey, true));
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
assertNotNull(contact);
assertEquals(contactId, contact.getId());
assertEquals(author, contact.getAuthor());
assertNull(contact.getAlias());
assertEquals(handshakePublicKey, contact.getHandshakePublicKey());
assertTrue(contact.isVerified());
assertEquals(author.getName(), contact.getAuthor().getName());
assertEquals(author.getPublicKey(), contact.getAuthor().getPublicKey());
assertEquals(author.getFormatVersion(),
contact.getAuthor().getFormatVersion());
// Ensure no contacts are returned after contact was deleted
db.removeContact(txn, contactId);
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
assertNull(contact);
db.commitTransaction(txn);
db.close();
}
@Test
public void testOfferedMessages() throws Exception {
Database<Connection> db = open(false);
@@ -1223,7 +1261,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and a message
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Attach some metadata to the message
Metadata metadata = new Metadata();
@@ -1294,7 +1332,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and a message
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Attach some metadata to the message
Metadata metadata = new Metadata();
@@ -1355,8 +1393,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and two messages
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message1, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
db.addMessage(txn, message1, DELIVERED, true, false, null);
// Attach some metadata to the messages
Metadata metadata = new Metadata();
@@ -1459,8 +1497,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and two messages
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message1, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
db.addMessage(txn, message1, DELIVERED, true, false, null);
// Attach some metadata to the messages
Metadata metadata = new Metadata();
@@ -1536,9 +1574,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and some messages
db.addGroup(txn, group);
db.addMessage(txn, message, PENDING, true, contactId);
db.addMessage(txn, message1, PENDING, true, contactId);
db.addMessage(txn, message2, INVALID, true, contactId);
db.addMessage(txn, message, PENDING, true, false, contactId);
db.addMessage(txn, message1, PENDING, true, false, contactId);
db.addMessage(txn, message2, INVALID, true, false, contactId);
// Add dependencies
db.addMessageDependency(txn, message, messageId1, PENDING);
@@ -1589,7 +1627,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(0, dependents.size());
// Add message 3
db.addMessage(txn, message3, UNKNOWN, false, contactId);
db.addMessage(txn, message3, UNKNOWN, false, false, contactId);
// Message 3 has message 1 as a dependent
dependents = db.getMessageDependents(txn, messageId3);
@@ -1601,7 +1639,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(0, dependents.size());
// Add message 4
db.addMessage(txn, message4, UNKNOWN, false, contactId);
db.addMessage(txn, message4, UNKNOWN, false, false, contactId);
// Message 4 has message 2 as a dependent
dependents = db.getMessageDependents(txn, messageId4);
@@ -1619,7 +1657,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and a message
db.addGroup(txn, group);
db.addMessage(txn, message, PENDING, true, contactId);
db.addMessage(txn, message, PENDING, true, false, contactId);
// Add a second group
Group group1 = getGroup(clientId, 123);
@@ -1629,7 +1667,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a message to the second group
Message message1 = getMessage(groupId1);
MessageId messageId1 = message1.getId();
db.addMessage(txn, message1, DELIVERED, true, contactId);
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
// Create an ID for a missing message
MessageId messageId2 = new MessageId(getRandomId());
@@ -1637,7 +1675,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add another message to the first group
Message message3 = getMessage(groupId);
MessageId messageId3 = message3.getId();
db.addMessage(txn, message3, DELIVERED, true, contactId);
db.addMessage(txn, message3, DELIVERED, true, false, contactId);
// Add dependencies between the messages
db.addMessageDependency(txn, message, messageId1, PENDING);
@@ -1680,10 +1718,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and some messages with different states
db.addGroup(txn, group);
db.addMessage(txn, message1, UNKNOWN, true, contactId);
db.addMessage(txn, message2, INVALID, true, contactId);
db.addMessage(txn, message3, PENDING, true, contactId);
db.addMessage(txn, message4, DELIVERED, true, contactId);
db.addMessage(txn, message1, UNKNOWN, true, false, contactId);
db.addMessage(txn, message2, INVALID, true, false, contactId);
db.addMessage(txn, message3, PENDING, true, false, contactId);
db.addMessage(txn, message4, DELIVERED, true, false, contactId);
Collection<MessageId> result;
@@ -1713,10 +1751,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and some messages
db.addGroup(txn, group);
db.addMessage(txn, message1, DELIVERED, true, contactId);
db.addMessage(txn, message2, DELIVERED, false, contactId);
db.addMessage(txn, message3, DELIVERED, false, contactId);
db.addMessage(txn, message4, DELIVERED, true, contactId);
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
db.addMessage(txn, message2, DELIVERED, false, false, contactId);
db.addMessage(txn, message3, DELIVERED, false, false, contactId);
db.addMessage(txn, message4, DELIVERED, true, false, contactId);
// Introduce dependencies between the messages
db.addMessageDependency(txn, message1, message2.getId(), DELIVERED);
@@ -1744,7 +1782,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// The message should not be sent or seen
MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
@@ -1878,7 +1916,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// The message should be visible to the contact
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
@@ -1961,7 +1999,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Add a group and a message
db.addGroup(txn, group);
db.addMessage(txn, message, UNKNOWN, false, contactId);
db.addMessage(txn, message, UNKNOWN, false, false, contactId);
// Walk the message through the validation and delivery states
assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
@@ -1988,7 +2026,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addMessage(txn, message, UNKNOWN, false, null);
db.addMessage(txn, message, UNKNOWN, false, false, null);
// There should be no messages to send
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
@@ -2073,7 +2111,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Time: now
// Retrieve the message from the database
@@ -2118,7 +2156,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addContact(txn, author, localAuthor.getId(), null, true));
db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message, DELIVERED, true, false, null);
// Time: now
// Retrieve the message from the database
@@ -2257,6 +2295,58 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close();
}
@Test
public void testTemporaryMessages() throws Exception {
Message message1 = getMessage(groupId);
MessageId messageId1 = message1.getId();
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a group and two temporary messages
db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, false, true, null);
db.addMessage(txn, message1, DELIVERED, false, true, null);
// Mark one of the messages as permanent
db.setMessagePermanent(txn, messageId);
// Remove all temporary messages
db.removeTemporaryMessages(txn);
// The permanent message should not have been removed
assertTrue(db.containsMessage(txn, messageId));
// The temporary message should have been removed
assertFalse(db.containsMessage(txn, messageId1));
db.commitTransaction(txn);
db.close();
}
@Test
public void testSyncVersions() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact
db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), null, true));
// Only sync version 0 should be supported by default
List<Byte> defaultSupported = singletonList((byte) 0);
assertEquals(defaultSupported, db.getSyncVersions(txn, contactId));
// Set the supported versions and check that they're returned
List<Byte> supported = asList((byte) 0, (byte) 1);
db.setSyncVersions(txn, contactId, supported);
assertEquals(supported, db.getSyncVersions(txn, contactId));
db.commitTransaction(txn);
db.close();
}
private Database<Connection> open(boolean resume) throws Exception {
return open(resume, new TestMessageFactory(), new SystemClock());
}

View File

@@ -0,0 +1,143 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.SettableClock;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TimeoutInputStreamTest extends BrambleTestCase {
private static final long TIMEOUT_MS = MINUTES.toMillis(1);
private final long now = System.currentTimeMillis();
private AtomicLong time;
private UnresponsiveInputStream in;
private AtomicBoolean listenerCalled;
private TimeoutInputStream stream;
private CountDownLatch readReturned;
@Before
public void setUp() {
time = new AtomicLong(now);
in = new UnresponsiveInputStream();
listenerCalled = new AtomicBoolean(false);
stream = new TimeoutInputStream(new SettableClock(time), in,
TIMEOUT_MS, stream -> listenerCalled.set(true));
readReturned = new CountDownLatch(1);
}
@Test
public void testTimeoutIsReportedIfReadDoesNotReturn() throws Exception {
startReading();
try {
// The stream should not report a timeout
assertFalse(stream.hasTimedOut());
// Time passes
time.set(now + TIMEOUT_MS);
// The stream still shouldn't report a timeout
assertFalse(stream.hasTimedOut());
// Time passes
time.set(now + TIMEOUT_MS + 1);
// The stream should report a timeout
assertTrue(stream.hasTimedOut());
// The listener should not have been called yet
assertFalse(listenerCalled.get());
// Close the stream
stream.close();
// The listener should have been called
assertTrue(listenerCalled.get());
} finally {
// Allow the read to return
in.readFinished.countDown();
}
}
@Test
public void testTimeoutIsNotReportedIfReadReturns() throws Exception {
startReading();
try {
// The stream should not report a timeout
assertFalse(stream.hasTimedOut());
// Time passes
time.set(now + TIMEOUT_MS);
// The stream still shouldn't report a timeout
assertFalse(stream.hasTimedOut());
// Allow the read to finish and wait for it to return
in.readFinished.countDown();
readReturned.await(10, SECONDS);
// Time passes
time.set(now + TIMEOUT_MS + 1);
// The stream should not report a timeout as the read has returned
assertFalse(stream.hasTimedOut());
// The listener should not have been called yet
assertFalse(listenerCalled.get());
// Close the stream
stream.close();
// The listener should have been called
assertTrue(listenerCalled.get());
} finally {
// Allow the read to return in case an assertion was thrown
in.readFinished.countDown();
}
}
private void startReading() throws Exception {
// Start a background thread to read from the unresponsive stream
new Thread(() -> {
try {
assertEquals(123, stream.read());
readReturned.countDown();
} catch (IOException e) {
fail();
}
}).start();
// Wait for the background thread to start reading
assertTrue(in.readStarted.await(10, SECONDS));
}
private class UnresponsiveInputStream extends InputStream {
private final CountDownLatch readStarted = new CountDownLatch(1);
private final CountDownLatch readFinished = new CountDownLatch(1);
@Override
public int read() throws IOException {
readStarted.countDown();
try {
readFinished.await();
return 123;
} catch (InterruptedException e) {
throw new IOException(e);
}
}
}
}

View File

@@ -42,6 +42,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(db).removeTemporaryMessages(txn);
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});

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