Compare commits

...

469 Commits

Author SHA1 Message Date
akwizgran
9d96ce6db0 Bump version numbers for 1.2.9 release. 2020-07-04 22:40:58 +01:00
akwizgran
45fb5bb445 Merge branch 'expiry-overflow-bug' into 'master'
Fix overflow in expiry warning code, bypass expiry code in release builds

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

Closes #1744

See merge request briar/briar!1258
2020-07-03 14:51:05 +00:00
akwizgran
597fef6d50 Check server socket is bound. 2020-07-03 13:04:26 +01:00
akwizgran
1dd15567de Merge branch 'style-guide-colours' into 'master'
Use colours from style guide

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

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

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

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

Closes #1712

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

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

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

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

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

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

Closes #1722

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

Closes #1714

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

Closes #1696

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

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

Closes #1528 and #1367

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

See merge request briar/briar!1237
2020-03-06 16:52:20 +00:00
akwizgran
8e21068465 Fix misreporting of wifi status in LAN plugin. 2020-03-06 13:35:06 +00:00
akwizgran
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
913 changed files with 23883 additions and 9459 deletions

View File

@@ -17,7 +17,7 @@ test:
script: script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest - ./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: after_script:
# these file change every time but should not be cached # these file change every time but should not be cached

View File

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

View File

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

View File

@@ -11,4 +11,14 @@ public interface BrambleAndroidEagerSingletons {
void inject(AndroidNetworkModule.EagerSingletons init); void inject(AndroidNetworkModule.EagerSingletons init);
void inject(ReportingModule.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 SocksModule.class
}) })
public class BrambleAndroidModule { 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.io.File;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject; import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir; import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir; import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
@@ -29,7 +32,11 @@ class AndroidAccountManager extends AccountManagerImpl
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidAccountManager.class.getName()); 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; protected final Context appContext;
private final SharedPreferences prefs; private final SharedPreferences prefs;
@@ -53,36 +60,6 @@ class AndroidAccountManager extends AccountManagerImpl
return exists; 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 @Override
public void deleteAccount() { public void deleteAccount() {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
@@ -105,14 +82,14 @@ class AndroidAccountManager extends AccountManagerImpl
return PreferenceManager.getDefaultSharedPreferences(appContext); return PreferenceManager.getDefaultSharedPreferences(appContext);
} }
// Locking: stateChangeLock @GuardedBy("stateChangeLock")
private void deleteAppData(SharedPreferences... clear) { private void deleteAppData(SharedPreferences... clear) {
// Clear and commit shared preferences // Clear and commit shared preferences
for (SharedPreferences prefs : clear) { for (SharedPreferences prefs : clear) {
if (!prefs.edit().clear().commit()) if (!prefs.edit().clear().commit())
LOG.warning("Could not clear shared preferences"); 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<>(); Set<File> files = new HashSet<>();
File dataDir = getDataDir(); File dataDir = getDataDir();
@Nullable @Nullable
@@ -121,14 +98,12 @@ class AndroidAccountManager extends AccountManagerImpl
LOG.warning("Could not list files in app data dir"); LOG.warning("Could not list files in app data dir");
} else { } else {
for (File file : fileArray) { for (File file : fileArray) {
String name = file.getName(); if (!PROTECTED_DIR_NAMES.contains(file.getName())) {
if (!name.equals("lib") && !name.equals("shared_prefs")) {
files.add(file); files.add(file);
} }
} }
} }
files.add(appContext.getFilesDir()); files.add(appContext.getFilesDir());
files.add(appContext.getCacheDir());
addIfNotNull(files, appContext.getExternalCacheDir()); addIfNotNull(files, appContext.getExternalCacheDir());
if (SDK_INT >= 19) { if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) { for (File file : appContext.getExternalCacheDirs()) {
@@ -140,12 +115,16 @@ class AndroidAccountManager extends AccountManagerImpl
addIfNotNull(files, file); 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) { for (File file : files) {
if (LOG.isLoggable(INFO)) {
LOG.info("Deleting " + file.getAbsolutePath());
}
deleteFileOrDir(file); 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() { 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.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI; 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.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static java.util.concurrent.TimeUnit.MINUTES; 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_ON);
filter.addAction(ACTION_SCREEN_OFF); filter.addAction(ACTION_SCREEN_OFF);
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); 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); if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
} }
@Override @Override
@@ -136,7 +137,8 @@ class AndroidNetworkManager implements NetworkManager, Service {
} }
private boolean isApEvent(@Nullable String action) { 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.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -24,13 +25,13 @@ import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -47,7 +48,10 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF; import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND; 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.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.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -63,6 +67,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final int MAX_DISCOVERY_MS = 10_000; private static final int MAX_DISCOVERY_MS = 10_000;
private final ScheduledExecutorService scheduler;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
private final Clock clock; private final Clock clock;
@@ -74,11 +79,14 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private volatile BluetoothAdapter adapter = null; private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, AndroidExecutor androidExecutor, TimeoutMonitor timeoutMonitor, Executor ioExecutor,
Context appContext, SecureRandom secureRandom, Clock clock, SecureRandom secureRandom, ScheduledExecutorService scheduler,
Backoff backoff, PluginCallback callback, int maxLatency) { AndroidExecutor androidExecutor, Context appContext, Clock clock,
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback, Backoff backoff, PluginCallback callback, int maxLatency,
maxLatency); int maxIdleTime) {
super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime);
this.scheduler = scheduler;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.clock = clock; this.clock = clock;
@@ -146,6 +154,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
wasEnabledByUs = true; wasEnabledByUs = true;
} }
@Override
void onAdapterDisabled() {
super.onAdapterDisabled();
wasEnabledByUs = false;
}
@Override @Override
@Nullable @Nullable
String getBluetoothAddress() { String getBluetoothAddress() {
@@ -170,9 +184,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return wrapSocket(ss.accept()); return wrapSocket(ss.accept());
} }
private DuplexTransportConnection wrapSocket(BluetoothSocket s) { private DuplexTransportConnection wrapSocket(BluetoothSocket s)
return new AndroidBluetoothTransportConnection(this, throws IOException {
connectionLimiter, s); return new AndroidBluetoothTransportConnection(this, connectionLimiter,
timeoutMonitor, appContext, scheduler, s);
} }
@Override @Override
@@ -240,11 +255,15 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
break; break;
} else if (ACTION_FOUND.equals(action)) { } else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE); BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
String address = d.getAddress(); // Ignore Bluetooth LE devices
if (LOG.isLoggable(INFO)) if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) {
LOG.info("Discovered " + scrubMacAddress(address)); String address = d.getAddress();
if (!addresses.contains(address)) if (LOG.isLoggable(INFO))
addresses.add(address); LOG.info("Discovered " +
scrubMacAddress(address));
if (!addresses.contains(address))
addresses.add(address);
}
} }
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
} }
@@ -260,7 +279,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
appContext.unregisterReceiver(receiver); appContext.unregisterReceiver(receiver);
} }
// Shuffle the addresses so we don't always try the same one first // Shuffle the addresses so we don't always try the same one first
Collections.shuffle(addresses); shuffle(addresses);
return addresses; return addresses;
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.content.Context; import android.content.Context;
import org.briarproject.bramble.api.event.EventBus; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -15,6 +16,7 @@ import org.briarproject.bramble.api.system.Clock;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -25,28 +27,34 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds 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 MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock; private final Clock clock;
private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor, public AndroidBluetoothPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, Context appContext, AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, Clock clock, SecureRandom secureRandom, EventBus eventBus, Clock clock,
BackoffFactory backoffFactory) { TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock; this.clock = clock;
this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
} }
@@ -63,12 +71,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter = BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl(); new BluetoothConnectionLimiterImpl(eventBus);
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, ioExecutor, androidExecutor, appContext, connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
secureRandom, clock, backoff, callback, MAX_LATENCY); scheduler, androidExecutor, appContext, clock, backoff,
callback, MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -1,33 +1,60 @@
package org.briarproject.bramble.plugin.bluetooth; package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.PowerManager;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.util.RenewableWakeLock;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.concurrent.ScheduledExecutorService;
import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.util.AndroidUtils.getWakeLockTag;
import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress;
@NotNullByDefault @NotNullByDefault
class AndroidBluetoothTransportConnection class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection { extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager; private final BluetoothConnectionLimiter connectionLimiter;
private final RenewableWakeLock wakeLock;
private final BluetoothSocket socket; private final BluetoothSocket socket;
private final InputStream in;
AndroidBluetoothTransportConnection(Plugin plugin, AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager, BluetoothConnectionLimiter connectionLimiter,
BluetoothSocket socket) { TimeoutMonitor timeoutMonitor, Context appContext,
ScheduledExecutorService scheduler, BluetoothSocket socket)
throws IOException {
super(plugin); super(plugin);
this.connectionManager = connectionManager; this.connectionLimiter = connectionLimiter;
this.socket = socket; this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2);
PowerManager powerManager = (PowerManager)
requireNonNull(appContext.getSystemService(POWER_SERVICE));
String tag = getWakeLockTag(appContext);
wakeLock = new RenewableWakeLock(powerManager, scheduler,
PARTIAL_WAKE_LOCK, tag, 1, MINUTES);
wakeLock.acquire();
String address = socket.getRemoteDevice().getAddress();
if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address);
} }
@Override @Override
protected InputStream getInputStream() throws IOException { protected InputStream getInputStream() {
return socket.getInputStream(); return in;
} }
@Override @Override
@@ -40,7 +67,8 @@ class AndroidBluetoothTransportConnection
try { try {
socket.close(); socket.close();
} finally { } finally {
connectionManager.connectionClosed(this); wakeLock.release();
connectionLimiter.connectionClosed(this);
} }
} }
} }

View File

@@ -1,25 +1,32 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network; import android.net.Network;
import android.net.NetworkInfo; import android.net.NetworkCapabilities;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.settings.Settings;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Collection; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -28,31 +35,25 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE; import static android.content.Context.WIFI_SERVICE;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.list;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener { class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidLanTcpPlugin.class.getName()); 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 Executor connectionStatusExecutor;
private final ConnectivityManager connectivityManager; private final ConnectivityManager connectivityManager;
@Nullable @Nullable
@@ -62,8 +63,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
AndroidLanTcpPlugin(Executor ioExecutor, Context appContext, AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
Backoff backoff, PluginCallback callback, int maxLatency, Backoff backoff, PluginCallback callback, int maxLatency,
int maxIdleTime) { int maxIdleTime, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime); super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
// Don't execute more than one connection status check at a time // Don't execute more than one connection status check at a time
connectionStatusExecutor = connectionStatusExecutor =
new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1); new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1);
@@ -79,34 +81,138 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@Override @Override
public void start() { public void start() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
running = true; initialisePortProperty();
Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE,
DEFAULT_PREF_PLUGIN_ENABLE));
updateConnectionStatus(); updateConnectionStatus();
} }
@Override
public void stop() {
running = false;
tryToClose(socket);
}
@Override @Override
protected Socket createSocket() throws IOException { protected Socket createSocket() throws IOException {
return socketFactory.createSocket(); return socketFactory.createSocket();
} }
@Override @Override
protected Collection<InetAddress> getLocalIpAddresses() { protected List<InetAddress> getUsableLocalInetAddresses(boolean ipv4) {
// If the device doesn't have wifi, don't open any sockets InetAddress addr = getWifiAddress(ipv4);
if (wifiManager == null) return emptyList(); return addr == null ? emptyList() : singletonList(addr);
// If we're connected to a wifi network, use that network }
@Nullable
private InetAddress getWifiAddress(boolean ipv4) {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
if (ipv4) return wifi == null ? null : wifi.getFirst();
// If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null) {
return SDK_INT >= 21 ? getWifiClientIpv6Address() : null;
}
// Use the wifi IPv4 address to determine which interface's IPv6
// address we should return (if the interface has a suitable address)
return getIpv6AddressForInterface(wifi.getFirst());
}
/**
* Returns a {@link Pair} where the first element is the IPv4 address of
* the wifi interface and the second element is true if this device is
* providing an access point, or false if this device is a client. Returns
* null if this device isn't connected to wifi as an access point or client.
*/
@Nullable
private Pair<InetAddress, Boolean> getWifiIpv4Address() {
if (wifiManager == null) return null;
// If we're connected to a wifi network, return its address
WifiInfo info = wifiManager.getConnectionInfo(); WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0) if (info != null && info.getIpAddress() != 0) {
return singletonList(intToInetAddress(info.getIpAddress())); return new Pair<>(intToInetAddress(info.getIpAddress()), false);
// If we're running an access point, return its address }
if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS)) List<InterfaceAddress> ifAddrs = getLocalInterfaceAddresses();
return singletonList(WIFI_AP_ADDRESS); // If we're providing a normal access point, return its address
// No suitable addresses for (InterfaceAddress ifAddr : ifAddrs) {
return emptyList(); if (isAndroidWifiApAddress(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true);
}
}
// If we're providing a wifi direct access point, return its address
for (InterfaceAddress ifAddr : ifAddrs) {
if (isAndroidWifiDirectApAddress(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true);
}
}
// Not connected to wifi
return null;
}
/**
* Returns true if the given address belongs to a network provided by an
* Android access point (including the access point's own address).
* <p>
* The access point's address is usually 192.168.43.1, but at least one
* device (Honor 8A) may use other addresses in the range 192.168.43.0/24.
*/
private boolean isAndroidWifiApAddress(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4
&& ip[0] == (byte) 192
&& ip[1] == (byte) 168
&& ip[2] == (byte) 43;
}
/**
* Returns true if the given address belongs to a network provided by an
* Android wifi direct legacy mode access point (including the access
* point's own address).
*/
private boolean isAndroidWifiDirectApAddress(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4
&& ip[0] == (byte) 192
&& ip[1] == (byte) 168
&& ip[2] == (byte) 49;
}
/**
* Returns a link-local IPv6 address for the wifi client interface, or null
* if there's no such interface or it doesn't have a suitable address.
*/
@TargetApi(21)
@Nullable
private InetAddress getWifiClientIpv6Address() {
for (Network net : connectivityManager.getAllNetworks()) {
NetworkCapabilities caps =
connectivityManager.getNetworkCapabilities(net);
if (caps == null || !caps.hasTransport(TRANSPORT_WIFI)) continue;
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) continue;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (isIpv6LinkLocalAddress(addr)) return addr;
}
}
return null;
}
/**
* Returns a link-local IPv6 address for the interface with the given IPv4
* address, or null if the interface doesn't have a suitable address.
*/
@Nullable
private InetAddress getIpv6AddressForInterface(InetAddress ipv4) {
try {
NetworkInterface iface = NetworkInterface.getByInetAddress(ipv4);
if (iface == null) return null;
for (InetAddress addr : list(iface.getInetAddresses())) {
if (isIpv6LinkLocalAddress(addr)) return addr;
}
// No suitable address
return null;
} catch (SocketException e) {
logException(LOG, WARNING, e);
return null;
}
} }
private InetAddress intToInetAddress(int ip) { private InetAddress intToInetAddress(int ip) {
@@ -128,9 +234,11 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
private SocketFactory getSocketFactory() { private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault(); if (SDK_INT < 21) return SocketFactory.getDefault();
for (Network net : connectivityManager.getAllNetworks()) { for (Network net : connectivityManager.getAllNetworks()) {
NetworkInfo info = connectivityManager.getNetworkInfo(net); NetworkCapabilities caps =
if (info != null && info.getType() == TYPE_WIFI) connectivityManager.getNetworkCapabilities(net);
if (caps != null && caps.hasTransport(TRANSPORT_WIFI)) {
return net.getSocketFactory(); return net.getSocketFactory();
}
} }
LOG.warning("Could not find suitable socket factory"); LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault(); return SocketFactory.getDefault();
@@ -138,30 +246,59 @@ class AndroidLanTcpPlugin extends LanTcpPlugin implements EventListener {
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
super.eventOccurred(e);
if (e instanceof NetworkStatusEvent) updateConnectionStatus(); if (e instanceof NetworkStatusEvent) updateConnectionStatus();
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
if (!running) return; State s = getState();
Collection<InetAddress> addrs = getLocalIpAddresses(); if (s != ACTIVE && s != INACTIVE) return;
if (addrs.contains(WIFI_AP_ADDRESS)) { Pair<InetAddress, Boolean> wifi = getPreferredWifiAddress();
if (wifi == null) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
// Server sockets may not have been closed automatically when
// interface was taken down. If any sockets are open, closing
// them here will cause the sockets to be cleared and the state
// to be updated in acceptContactConnections()
if (s == ACTIVE) {
LOG.info("Closing server sockets");
tryToClose(state.getServerSocket(true), LOG, WARNING);
tryToClose(state.getServerSocket(false), LOG, WARNING);
}
} else if (wifi.getSecond()) {
LOG.info("Providing wifi hotspot"); LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way // There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to // to get a suitable socket factory, so we won't be able to
// make outgoing connections on API 21+ if another network // make outgoing connections on API 21+ if another network
// has internet access // has internet access
socketFactory = SocketFactory.getDefault(); socketFactory = SocketFactory.getDefault();
if (socket == null || socket.isClosed()) bind(); if (s == INACTIVE) bind();
} else if (addrs.isEmpty()) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
tryToClose(socket);
} else { } else {
LOG.info("Connected to wifi"); LOG.info("Connected to wifi");
socketFactory = getSocketFactory(); socketFactory = getSocketFactory();
if (socket == null || socket.isClosed()) bind(); if (s == INACTIVE) bind();
} }
}); });
} }
/**
* Returns a {@link Pair} where the first element is an IP address (IPv4 if
* available, otherwise IPv6) of the wifi interface and the second element
* is true if this device is providing an access point, or false if this
* device is a client. Returns null if this device isn't connected to wifi
* as an access point or client.
*/
@Nullable
private Pair<InetAddress, Boolean> getPreferredWifiAddress() {
Pair<InetAddress, Boolean> wifi = getWifiIpv4Address();
// If there's no wifi IPv4 address, we might be a client on an
// IPv6-only wifi network. We can only detect this on API 21+
if (wifi == null && SDK_INT >= 21) {
InetAddress ipv6 = getWifiClientIpv6Address();
if (ipv6 != null) return new Pair<>(ipv6, false);
}
return wifi;
}
} }

View File

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

View File

@@ -27,6 +27,7 @@ import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static org.briarproject.bramble.util.AndroidUtils.getWakeLockTag;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -53,7 +54,7 @@ class AndroidTorPlugin extends TorPlugin {
appContext.getSystemService(POWER_SERVICE); appContext.getSystemService(POWER_SERVICE);
if (pm == null) throw new AssertionError(); if (pm == null) throw new AssertionError();
wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK, wakeLock = new RenewableWakeLock(pm, scheduler, PARTIAL_WAKE_LOCK,
getWakeLockTag(), 1, MINUTES); getWakeLockTag(appContext), 1, MINUTES);
} }
@Override @Override
@@ -74,7 +75,6 @@ class AndroidTorPlugin extends TorPlugin {
@Override @Override
protected void enableNetwork(boolean enable) throws IOException { protected void enableNetwork(boolean enable) throws IOException {
if (!running) return;
if (enable) wakeLock.acquire(); if (enable) wakeLock.acquire();
super.enableNetwork(enable); super.enableNetwork(enable);
if (!enable) wakeLock.release(); if (!enable) wakeLock.release();
@@ -85,17 +85,4 @@ class AndroidTorPlugin extends TorPlugin {
super.stop(); super.stop();
wakeLock.release(); wakeLock.release();
} }
private String getWakeLockTag() {
PackageManager pm = appContext.getPackageManager();
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
}
return getClass().getSimpleName();
}
} }

View File

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

View File

@@ -61,12 +61,12 @@ class AndroidLocationUtils implements LocationUtils {
private String getCountryFromPhoneNetwork() { private String getCountryFromPhoneNetwork() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE); Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o; TelephonyManager tm = (TelephonyManager) o;
return tm.getNetworkCountryIso(); return tm == null ? "" : tm.getNetworkCountryIso();
} }
private String getCountryFromSimCard() { private String getCountryFromSimCard() {
Object o = appContext.getSystemService(TELEPHONY_SERVICE); Object o = appContext.getSystemService(TELEPHONY_SERVICE);
TelephonyManager tm = (TelephonyManager) o; TelephonyManager tm = (TelephonyManager) o;
return tm.getSimCountryIso(); return tm == null ? "" : tm.getSimCountryIso();
} }
} }

View File

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

View File

@@ -3,18 +3,30 @@ package org.briarproject.bramble.util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File; 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.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@NotNullByDefault
public class AndroidUtils { public class AndroidUtils {
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later // Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
@@ -22,11 +34,10 @@ public class AndroidUtils {
private static final String STORED_REPORTS = "dev-reports"; private static final String STORED_REPORTS = "dev-reports";
@SuppressWarnings("deprecation")
public static Collection<String> getSupportedArchitectures() { public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>(); List<String> abis = new ArrayList<>();
if (SDK_INT >= 21) { if (SDK_INT >= 21) {
abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS)); abis.addAll(asList(Build.SUPPORTED_ABIS));
} else { } else {
abis.add(Build.CPU_ABI); abis.add(Build.CPU_ABI);
if (Build.CPU_ABI2 != null) abis.add(Build.CPU_ABI2); if (Build.CPU_ABI2 != null) abis.add(Build.CPU_ABI2);
@@ -36,25 +47,89 @@ public class AndroidUtils {
public static String getBluetoothAddress(Context ctx, public static String getBluetoothAddress(Context ctx,
BluetoothAdapter adapter) { 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 // Return the adapter's address if it's valid and not fake
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
String address = adapter.getAddress(); 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 // Return the address from settings if it's valid and not fake
address = Settings.Secure.getString(ctx.getContentResolver(), address = Settings.Secure.getString(ctx.getContentResolver(),
"bluetooth_address"); "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 // 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) return !StringUtils.isNullOrEmpty(address)
&& BluetoothAdapter.checkBluetoothAddress(address) && BluetoothAdapter.checkBluetoothAddress(address)
&& !address.equals(FAKE_BLUETOOTH_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) { public static File getReportDir(Context ctx) {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE); 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"};
}
public static String getWakeLockTag(Context ctx) {
PackageManager pm = ctx.getPackageManager();
for (PackageInfo info : pm.getInstalledPackages(0)) {
String name = info.packageName.toLowerCase();
if (name.startsWith("com.huawei.powergenie")) {
return "LocationManagerService";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
}
return ctx.getPackageName();
}
} }

View File

@@ -16,13 +16,10 @@ import org.junit.Test;
import java.io.File; import java.io.File;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; 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.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.toHexString;
public class AndroidAccountManagerTest extends BrambleMockTestCase { public class AndroidAccountManagerTest extends BrambleMockTestCase {
@@ -40,11 +37,8 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
private final Application app; private final Application app;
private final ApplicationInfo applicationInfo; private final ApplicationInfo applicationInfo;
private final String encryptedKeyHex = toHexString(getRandomBytes(123));
private final File testDir = getTestDirectory(); private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key"); 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 final File dbDir = new File(testDir, "db");
private AndroidAccountManager accountManager; 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 @Test
public void testDeleteAccountClearsSharedPrefsAndDeletesFiles() public void testDeleteAccountClearsSharedPrefsAndDeletesFiles()
throws Exception { 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 libDir = new File(testDir, "lib");
File libFile = new File(libDir, "file"); File libFile = new File(libDir, "file");
File sharedPrefsDir = new File(testDir, "shared_prefs"); File sharedPrefsDir = new File(testDir, "shared_prefs");
@@ -140,6 +113,8 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertTrue(dbDir.mkdirs()); assertTrue(dbDir.mkdirs());
assertTrue(keyDir.mkdirs()); assertTrue(keyDir.mkdirs());
assertTrue(codeCacheDir.mkdirs());
assertTrue(codeCacheFile.createNewFile());
assertTrue(libDir.mkdirs()); assertTrue(libDir.mkdirs());
assertTrue(libFile.createNewFile()); assertTrue(libFile.createNewFile());
assertTrue(sharedPrefsDir.mkdirs()); assertTrue(sharedPrefsDir.mkdirs());
@@ -155,6 +130,8 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(dbDir.exists()); assertFalse(dbDir.exists());
assertFalse(keyDir.exists()); assertFalse(keyDir.exists());
assertTrue(codeCacheDir.exists());
assertTrue(codeCacheFile.exists());
assertTrue(libDir.exists()); assertTrue(libDir.exists());
assertTrue(libFile.exists()); assertTrue(libFile.exists());
assertTrue(sharedPrefsDir.exists()); assertTrue(sharedPrefsDir.exists());

View File

@@ -1,44 +1,46 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', '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:protos:26.5.1:protos-26.5.1.jar:8dde1130725461fe827f2a343d353f2b51e8870661fc860d7d5ebddb097ead4e',
'com.android.tools.analytics-library:shared:26.4.0:shared-26.4.0.jar:1332106a905d48909c81268c9e414946de3e83487db394c6073b0a9b5c3d0ed2', 'com.android.tools.analytics-library:shared:26.5.1:shared-26.5.1.jar:ccc2f3b00ec17b11401610ba68553544fc8fc517120e84439ac6eb86b875e18d',
'com.android.tools.analytics-library:tracker:26.4.0:tracker-26.4.0.jar:d0020cfbfd4cd75935f2972d6a24089840d4a10df6f3ef2a796093217dd37796', 'com.android.tools.analytics-library:tracker:26.5.1:tracker-26.5.1.jar:3a76984c0fe2e847ca7a8b35b4780ef0447a9d1666946cb8e60466318e0ab5ae',
'com.android.tools.build:apksig:3.4.0:apksig-3.4.0.jar:91d5a1866139c69756280355a6f61b4d619d0516841580114f45a10f2177327e', 'com.android.tools.build:aapt2-proto:0.4.0:aapt2-proto-0.4.0.jar:fac0435e08898f89eeeb9ca236bea707155ff816c12205ced285ad53604133ca',
'com.android.tools.build:apkzlib:3.4.0:apkzlib-3.4.0.jar:8653c85f5fdf1dde840e8b8af7396aeb79c34b66e541b5860059616006535592', 'com.android.tools.build:apksig:3.5.1:apksig-3.5.1.jar:1fd33e7f009a2a0da766cfeec4211a09f548034b015c289a66d75dd8a9302f4a',
'com.android.tools.build:builder-model:3.4.0:builder-model-3.4.0.jar:a88f138124a9f016a70bcb4760359a502f65c7deed56507ee4014f4dd9ea853b', 'com.android.tools.build:apkzlib:3.5.1:apkzlib-3.5.1.jar:9f330167cbe973b7db407692f74f4f6453b7ffa5f2048934b06280c2ceee60fa',
'com.android.tools.build:builder-test-api:3.4.0:builder-test-api-3.4.0.jar:31089ab1ec19ca7687a010867d2f3807513c805b8226979706f4247b5d4df26f', 'com.android.tools.build:builder-model:3.5.1:builder-model-3.5.1.jar:39ea3c82b76b6e0c9f9fa88d93e0edc1dd4a0f1dfae0ef6fbf2d451da47e5450',
'com.android.tools.build:builder:3.4.0:builder-3.4.0.jar:476221b5203a7f50089bf185ed95000a34b6f5020ef0a17815afd58606922679', 'com.android.tools.build:builder-test-api:3.5.1:builder-test-api-3.5.1.jar:a1b59305584cbcaa078fdc9cfb80871012755b822dd32e8da19add6f7bbcb762',
'com.android.tools.build:gradle-api:3.4.0:gradle-api-3.4.0.jar:215eca38f6719213c2f492b4d622cdd11676c66c9871f8a2aed0c66d00175628', 'com.android.tools.build:builder:3.5.1:builder-3.5.1.jar:e3a8d382434c5f60990730c4719fc814e85a898a33a1e96c1df8d627d3c6eea6',
'com.android.tools.build:manifest-merger:26.4.0:manifest-merger-26.4.0.jar:29e45e690dedd165035e97c21c2ca94d0bd4ec16b6b210daa26669a582b6f220', 'com.android.tools.build:gradle-api:3.5.1:gradle-api-3.5.1.jar:be9b41859bace11998f66b04ed944f87e413f3ad6da3c4665587699da125addc',
'com.android.tools.ddms:ddmlib:26.4.0:ddmlib-26.4.0.jar:93f56fe4630c3166adbd6c51d7bb602d96abb91b07ba5b1165fdcd071e88c940', 'com.android.tools.build:manifest-merger:26.5.1:manifest-merger-26.5.1.jar:dcad9ecb967251f4d750f55a4204a2b400e8fbfe5cb930a1d0d5dbe10ae8bdfc',
'com.android.tools.external.com-intellij:intellij-core:26.4.0:intellij-core-26.4.0.jar:30cb0e879d4424de9677a50b537fb628636b4a50f5470af5e52437980c41421f', 'com.android.tools.ddms:ddmlib:26.5.1:ddmlib-26.5.1.jar:b081aef2a4ed3f4d47cae4cdb128469735f25a114e026d37123bf9ffdec742a8',
'com.android.tools.external.com-intellij:kotlin-compiler:26.4.0:kotlin-compiler-26.4.0.jar:dd1fe225c31a0e012dc025336363a5b783e2c5c20ffb69e77f8f57e89420d998', 'com.android.tools.external.com-intellij:intellij-core:26.5.1:intellij-core-26.5.1.jar:20eced30adc124805bd93488d9cd9d3e33e6bf7b48e9fe5a703d4983f894d450',
'com.android.tools.external.org-jetbrains:uast:26.4.0:uast-26.4.0.jar:f25f3285b775a983327583ff6584dea54e447813ef69e0ce08b05a45b5f4aab0', 'com.android.tools.external.com-intellij:kotlin-compiler:26.5.1:kotlin-compiler-26.5.1.jar:5aed762dd54875b77ae7018d97c05756ff0c5b9fd02ec595dd396ccd14cc22cb',
'com.android.tools.layoutlib:layoutlib-api:26.4.0:layoutlib-api-26.4.0.jar:52128f5cf293b224072be361919bfd416e59480ab7264ddcdbbf046b0d7a12e3', 'com.android.tools.external.org-jetbrains:uast:26.5.1:uast-26.5.1.jar:4bc8653d6c0943f40fee963a149e36c6baa45683d2530968a13f5007e3c40740',
'com.android.tools.lint:lint-api:26.4.0:lint-api-26.4.0.jar:fdb8fca8ae4c254f438338d03d72605e00ed106f2d5550405af41ca1c8509401', 'com.android.tools.layoutlib:layoutlib-api:26.5.1:layoutlib-api-26.5.1.jar:88732f11396c427273e515d23042e35633f4fe4295528a99b866aa2adf0efd9c',
'com.android.tools.lint:lint-checks:26.4.0:lint-checks-26.4.0.jar:4ff52d40488cd3e22b9c6b2eb67784e0c3269d0b42ef9d17689cd75a7b2bceb4', 'com.android.tools.lint:lint-api:26.5.1:lint-api-26.5.1.jar:ec33fcd72bfaf70dd841e03fbfd93f109c2e575aec146067c606689c3972f0de',
'com.android.tools.lint:lint-gradle-api:26.4.0:lint-gradle-api-26.4.0.jar:714b7a85c7d2aa10daeab16e969fe7530c659d0728a7f24021da456870418d0f', 'com.android.tools.lint:lint-checks:26.5.1:lint-checks-26.5.1.jar:a1b9607d484aaae7a71dcecdc76f8003d8239af226c776894a2cf63f9e6c60d7',
'com.android.tools.lint:lint-gradle:26.4.0:lint-gradle-26.4.0.jar:b8c130d273f522388734457e1b96790f41528fcec6fda9e8eaa4e4d95a07cfbb', 'com.android.tools.lint:lint-gradle-api:26.5.1:lint-gradle-api-26.5.1.jar:82453fd98a8394cc84ed995c04d2cd744abd1d6589403427ba7eef53115406f3',
'com.android.tools.lint:lint:26.4.0:lint-26.4.0.jar:83aa062fb0405b60ed358d858c8c2955e1bae44a455b498068c6a60988755f00', 'com.android.tools.lint:lint-gradle:26.5.1:lint-gradle-26.5.1.jar:59465b56cf7db77c656d5f8195d721c3d48b6bdd0502d774de335bfe4baff00b',
'com.android.tools:annotations:26.4.0:annotations-26.4.0.jar:a7955b8e19c3a2a861d6faa43a58b7c0d46ea9112188ee3e235c6f9f439ecc1a', 'com.android.tools.lint:lint:26.5.1:lint-26.5.1.jar:336e4b04ec6f8b0f25879131b7a7862d77df83a1879ee5b71be26128755f8e2e',
'com.android.tools:common:26.4.0:common-26.4.0.jar:ea40b94b3c1284ea7700f011388e2906a8363a66abd902891722b3c557984852', 'com.android.tools:annotations:26.5.1:annotations-26.5.1.jar:2c43c82f8c59d8f7a61e3239e1a2dc9f69dc342ec09af9b7c9f69b25337c0b6e',
'com.android.tools:dvlib:26.4.0:dvlib-26.4.0.jar:23af89c535b01ba36ceed1b6b309b672814eba624e643cd7dedf0519edad50cc', 'com.android.tools:common:26.5.1:common-26.5.1.jar:eccfa54486ed54c4e3123cc42195d023bd0dd21bcd2f0e4868e8c6fc70f8ef6b',
'com.android.tools:repository:26.4.0:repository-26.4.0.jar:3d1763ab46199374dc6d94129bba11c70f1d5857e2c81a3ac4898abca40b176b', 'com.android.tools:dvlib:26.5.1:dvlib-26.5.1.jar:46f93ad498b4756e7d867d2fe38c38890a80e7407a4ae459e4a8c8d5c5aeacfe',
'com.android.tools:sdk-common:26.4.0:sdk-common-26.4.0.jar:78a522525b30ffc6b7bf1299c831d24ce385f68a9f4878f8f752e9baefa31b0f', 'com.android.tools:repository:26.5.1:repository-26.5.1.jar:2b3ee791aa4c3e8ce60498c161a27ca7228816fc630eed4d9f25f2f36a106dce',
'com.android.tools:sdklib:26.4.0:sdklib-26.4.0.jar:b854c23892013a326d761cf071c72cf3e038ed0469d10f4a356829fa56e4c132', 'com.android.tools:sdk-common:26.5.1:sdk-common-26.5.1.jar:365f749676c3574676fd465177c8a492f340816db2b520d6ed114d3b6e77bea7',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed', '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.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.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.dagger:dagger-compiler:2.22.1:dagger-compiler-2.22.1.jar:e5f28302cbe70a79d3620cddebfb8ec0736814f3980ffe1e673bfe3342f507d3', 'com.google.dagger:dagger-compiler:2.24:dagger-compiler-2.24.jar:3c5afb955fb188da485cb2c048eff37dce0e1530b9780a0f2f7187d16d1ccc1f',
'com.google.dagger:dagger-producers:2.22.1:dagger-producers-2.22.1.jar:f834a0082014213a68ff06a0f048d750178d02196c58b0b15beb367d32b97e35', 'com.google.dagger:dagger-producers:2.24:dagger-producers-2.24.jar:f10f45b95191954d5d6b043fca9e62fb621d21bf70634b8f8476c7988b504c3a',
'com.google.dagger:dagger-spi:2.22.1:dagger-spi-2.22.1.jar:4b0b922793b3bcb91b99fabb75dba77c68afd7ae4c5f0c4fd6ba681f0a291c7d', 'com.google.dagger:dagger-spi:2.24:dagger-spi-2.24.jar:c038445d14dbcb4054e61bf49e05009edf26fce4fdc7ec1a9db544784f68e718',
'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a', 'com.google.dagger:dagger:2.24:dagger-2.24.jar:550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64',
'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8', '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.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.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:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:26.0-jre:guava-26.0-jre.jar:a0e9cabad665bc20bcd2b01f108e5fc03f756e13aea80abaadb9f407033bea2c', '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.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd', 'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4', '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.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', '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', '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.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2', 'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache.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.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.9:obfs4proxy-android-0.0.9.zip:9b7e9181535ea8d8bbe8ae6338e08cf4c5fc1e357a779393e0ce49586d459ae0', 'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.8:tor-android-0.3.5.8.zip:42a13a6f185be1a62f42e3f30ce66a3c099ac5ec890a65e7593111b65b44a54a', '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-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0', '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-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: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.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-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', '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-reflect:1.3.50:kotlin-reflect-1.3.50.jar:64583199ea5a54aefd1bd1595288925f784226ee562d1dd279011c6075b3d7a4',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.21:kotlin-stdlib-common-1.3.21.jar:cea61f7b611895e64f58569a9757fc0ab0d582f107211e1930e0ce2a0add52a7', '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.21:kotlin-stdlib-jdk7-1.3.21.jar:a87875604fd42140da6938ae4d35ee61081f4482536efc6d2615b8b626a198af', '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.21:kotlin-stdlib-jdk8-1.3.21.jar:5823ed66ac122a1c55442ebca5a209a843ccd87f562edc31a787f3d2e47f74d4', 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50:kotlin-stdlib-jdk8-1.3.50.jar:1b351fb6e09c14b55525c74c1f4cf48942eae43c348b7bc764a5e6e423d4da0c',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.21:kotlin-stdlib-1.3.21.jar:38ba2370d9f06f50433e06b2ca775b94473c2e2785f410926079ab793c72b034', '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.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478', 'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c', 'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',

View File

@@ -7,7 +7,7 @@ apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
dependencies { 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' implementation 'com.google.code.findbugs:jsr305:3.0.2'
testImplementation 'junit:junit:4.12' 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; 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.crypto.SecretKey;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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 * Returns true if the manager has the database key. This will be false
* before {@link #createAccount(String, String)} or {@link #signIn(String)} * before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and true after {@link #createAccount(String, 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(); boolean hasDatabaseKey();
@@ -22,25 +24,22 @@ public interface AccountManager {
* before {@link #createAccount(String, String)} or {@link #signIn(String)} * before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and non-null after * has been called, and non-null after
* {@link #createAccount(String, String)} or {@link #signIn(String)} has * {@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 @Nullable
SecretKey getDatabaseKey(); SecretKey getDatabaseKey();
/** /**
* Returns true if the encrypted database key can be loaded from disk, and * Returns true if the encrypted database key can be loaded from disk.
* the database directory exists and is a directory.
*/ */
boolean accountExists(); boolean accountExists();
/** /**
* Creates an identity with the given name and registers it with the * Creates an identity with the given name and registers it with the
* {@link IdentityManager}. Creates a database key, encrypts it with the * {@link IdentityManager}. Creates a database key, encrypts it with the
* given password and stores it on disk. * given password and stores it on disk. {@link #accountExists()} will
* <p/> * return true after this method returns true.
* This method does not create the database directory, so
* {@link #accountExists()} will continue to return false until the
* database directory is created.
*/ */
boolean createAccount(String name, String password); 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 * Loads the encrypted database key from disk and decrypts it with the
* given password. * 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 * Loads the encrypted database key from disk, decrypts it with the old
* password, encrypts it with the new password, and stores it on disk, * password, encrypts it with the new password, and stores it on disk,
* replacing the old key. * replacing the old key.
* *
* @return true if the database key was successfully loaded, re-encrypted * @throws DecryptionException If the database key could not be loaded and
* and stored. * 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; throws DbException, FormatException;
void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata, 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) Message createMessage(GroupId g, long timestamp, BdfList body)
throws FormatException; throws FormatException;
@@ -108,7 +111,7 @@ public interface ClientHelper {
Author parseAndValidateAuthor(BdfList author) throws FormatException; Author parseAndValidateAuthor(BdfList author) throws FormatException;
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes) PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
throws FormatException; throws FormatException;
TransportProperties parseAndValidateTransportProperties( TransportProperties parseAndValidateTransportProperties(
BdfDictionary properties) throws FormatException; BdfDictionary properties) throws FormatException;

View File

@@ -1,8 +1,11 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.connection;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault @NotNullByDefault

View File

@@ -0,0 +1,130 @@
package org.briarproject.bramble.api.connection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.sync.Priority;
import java.util.Collection;
/**
* Keeps track of which contacts are currently connected by which transports.
*/
@NotNullByDefault
public interface ConnectionRegistry {
/**
* Registers an incoming connection from the given contact over the given
* transport. The connection's {@link Priority priority} can be set later
* via {@link #setPriority(ContactId, TransportId, InterruptibleConnection,
* Priority)} if a priority record is received from the contact.
* <p>
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ContactConnectedEvent} if this is the only connection with the
* contact.
*/
void registerIncomingConnection(ContactId c, TransportId t,
InterruptibleConnection conn);
/**
* Registers an outgoing connection to the given contact over the given
* transport.
* <p>
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ContactConnectedEvent} if this is the only connection with the
* contact.
* <p>
* If the registry has any "better" connections with the given contact, the
* given connection will be interrupted. If the registry has any "worse"
* connections with the given contact, those connections will be
* interrupted.
* <p>
* Connection A is considered "better" than connection B if both
* connections have had their priorities set, and either A's transport is
* {@link PluginConfig#getTransportPreferences() preferred} to B's, or
* they use the same transport and A has higher {@link Priority priority}
* than B.
* <p>
* For backward compatibility, connections without priorities are not
* considered better or worse than other connections.
*/
void registerOutgoingConnection(ContactId c, TransportId t,
InterruptibleConnection conn, Priority priority);
/**
* Unregisters a connection with the given contact over the given transport.
* <p>
* Broadcasts {@link ConnectionClosedEvent}. Also broadcasts
* {@link ContactDisconnectedEvent} if this is the only connection with
* the contact.
*/
void unregisterConnection(ContactId c, TransportId t,
InterruptibleConnection conn, boolean incoming, boolean exception);
/**
* Sets the {@link Priority priority} of a connection that was previously
* registered via {@link #registerIncomingConnection(ContactId, TransportId,
* InterruptibleConnection)}.
* <p>
* If the registry has any "better" connections with the given contact, the
* given connection will be interrupted. If the registry has any "worse"
* connections with the given contact, those connections will be
* interrupted.
* <p>
* Connection A is considered "better" than connection B if both
* connections have had their priorities set, and either A's transport is
* {@link PluginConfig#getTransportPreferences() preferred} to B's, or
* they use the same transport and A has higher {@link Priority priority}
* than B.
* <p>
* For backward compatibility, connections without priorities are not
* considered better or worse than other connections.
*/
void setPriority(ContactId c, TransportId t, InterruptibleConnection conn,
Priority priority);
/**
* Returns any contacts that are connected via the given transport.
*/
Collection<ContactId> getConnectedContacts(TransportId t);
/**
* Returns any contacts that are connected via the given transport or any
* {@link PluginConfig#getTransportPreferences() better} transport.
*/
Collection<ContactId> getConnectedOrBetterContacts(TransportId t);
/**
* Returns true if the given contact is connected via the given transport.
*/
boolean isConnected(ContactId c, TransportId t);
/**
* Returns true if the given contact is connected via any transport.
*/
boolean isConnected(ContactId c);
/**
* Registers a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionOpenedEvent} if this is the only connection
* with the pending contact.
*
* @return True if this is the only connection with the pending contact,
* false if it is redundant and should be closed
*/
boolean registerConnection(PendingContactId p);
/**
* Unregisters a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionClosedEvent}.
*/
void unregisterConnection(PendingContactId p, boolean success);
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.bramble.api.connection;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* A duplex sync connection that can be closed by interrupting its outgoing
* sync session.
*/
@NotNullByDefault
public interface InterruptibleConnection {
/**
* Interrupts the connection's outgoing sync session. If the underlying
* transport connection is alive and the remote peer is cooperative, this
* should result in both sync sessions ending and the connection being
* cleanly closed.
*/
void interruptOutgoingSession();
}

View File

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

View File

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

View File

@@ -132,17 +132,33 @@ public interface CryptoComponent {
* storage. The encryption and authentication keys are derived from the * storage. The encryption and authentication keys are derived from the
* given password. The ciphertext will be decryptable using the same * given password. The ciphertext will be decryptable using the same
* password after the app restarts. * 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 * Decrypts and authenticates the given ciphertext that has been read from
* storage. The encryption and authentication keys are derived from the * 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). * 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. * 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 org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -77,12 +78,12 @@ public interface DatabaseComponent extends TransactionManager {
* Stores a local message. * Stores a local message.
*/ */
void addLocalMessage(Transaction txn, Message m, Metadata meta, void addLocalMessage(Transaction txn, Message m, Metadata meta,
boolean shared) throws DbException; boolean shared, boolean temporary) throws DbException;
/** /**
* Stores a pending contact. * Stores a pending contact.
*/ */
void addPendingContact(Transaction txn, PendingContact p) void addPendingContact(Transaction txn, PendingContact p, AuthorId local)
throws DbException; throws DbException;
/** /**
@@ -427,6 +428,13 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
Settings getSettings(Transaction txn, String namespace) throws DbException; 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. * Returns all transport keys for the given transport.
* <p/> * <p/>
@@ -510,6 +518,12 @@ public interface DatabaseComponent extends TransactionManager {
void removePendingContact(Transaction txn, PendingContactId p) void removePendingContact(Transaction txn, PendingContactId p)
throws DbException; 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. * 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, void setGroupVisibility(Transaction txn, ContactId c, GroupId g,
Visibility v) throws DbException; 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. * Marks the given message as shared.
*/ */
@@ -568,6 +587,12 @@ public interface DatabaseComponent extends TransactionManager {
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t, void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
long timePeriod, long base, byte[] bitmap) throws DbException; 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. * Marks the given transport keys as usable for outgoing streams.
*/ */

View File

@@ -1,13 +1,29 @@
package org.briarproject.bramble.api.db; package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File; import java.io.File;
import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface DatabaseConfig { public interface DatabaseConfig {
/**
* Returns the directory where the database stores its data.
*/
File getDatabaseDirectory(); File getDatabaseDirectory();
/**
* Returns the directory where the encrypted database key is stored.
*/
File getDatabaseKeyDirectory(); 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; 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 * Thrown when a duplicate pending contact is added to the database. This
* exception may occur due to concurrent updates and does not indicate a * exception may occur due to concurrent updates and does not indicate a
* database error. * database error.
*/ */
public class PendingContactExistsException extends DbException { public class PendingContactExistsException extends DbException {
private final PendingContact pendingContact;
public PendingContactExistsException(PendingContact pendingContact) {
this.pendingContact = pendingContact;
}
public PendingContact getPendingContact() {
return pendingContact;
}
} }

View File

@@ -18,6 +18,8 @@ public interface EventBus {
/** /**
* Asynchronously notifies all listeners of an event. Listeners are * Asynchronously notifies all listeners of an event. Listeners are
* notified on the {@link EventExecutor}. * notified on the {@link EventExecutor}.
* <p>
* This method can safely be called while holding a lock.
*/ */
void broadcast(Event e); void broadcast(Event e);
} }

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

@@ -6,8 +6,10 @@ public interface BluetoothConstants {
int UUID_BYTES = 16; int UUID_BYTES = 16;
// Transport properties
String PROP_ADDRESS = "address"; String PROP_ADDRESS = "address";
String PROP_UUID = "uuid"; String PROP_UUID = "uuid";
String PREF_BT_ENABLE = "enable"; // Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = false;
} }

View File

@@ -1,67 +0,0 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import java.util.Collection;
/**
* Keeps track of which contacts are currently connected by which transports.
*/
@NotNullByDefault
public interface ConnectionRegistry {
/**
* Registers a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ContactConnectedEvent} if this is the only connection with the
* contact.
*/
void registerConnection(ContactId c, TransportId t, boolean incoming);
/**
* Unregisters a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionClosedEvent}. Also broadcasts
* {@link ContactDisconnectedEvent} if this is the only connection with
* the contact.
*/
void unregisterConnection(ContactId c, TransportId t, boolean incoming);
/**
* Returns any contacts that are connected via the given transport.
*/
Collection<ContactId> getConnectedContacts(TransportId t);
/**
* Returns true if the given contact is connected via the given transport.
*/
boolean isConnected(ContactId c, TransportId t);
/**
* Returns true if the given contact is connected via any transport.
*/
boolean isConnected(ContactId c);
/**
* Registers a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionOpenedEvent} if this is the only connection
* with the pending contact.
*
* @return True if this is the only connection with the pending contact,
* false if it is redundant and should be closed
*/
boolean registerConnection(PendingContactId p);
/**
* Unregisters a connection with the given pending contact. Broadcasts
* {@link RendezvousConnectionClosedEvent}.
*/
void unregisterConnection(PendingContactId p, boolean success);
}

View File

@@ -4,10 +4,15 @@ public interface LanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.lan"); 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_IP_PORTS = "ipPorts";
String PROP_PORT = "port";
String PROP_IPV6 = "ipv6";
// a local setting // Local settings (not shared with contacts)
String PREF_LAN_IP_PORTS = "ipPorts"; String PREF_LAN_IP_PORTS = "ipPorts";
String PREF_IPV6 = "ipv6";
// Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;
} }

View File

@@ -3,12 +3,55 @@ package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.Collection; import java.util.Collection;
@NotNullByDefault @NotNullByDefault
public interface Plugin { public interface Plugin {
enum State {
/**
* The plugin has not finished starting or has been stopped.
*/
STARTING_STOPPING,
/**
* The plugin is disabled by settings. Use {@link #getReasonsDisabled()}
* to find out which settings are responsible.
*/
DISABLED,
/**
* The plugin is being enabled and can't yet make or receive
* connections.
*/
ENABLING,
/**
* The plugin is enabled and can make or receive connections.
*/
ACTIVE,
/**
* The plugin is enabled but can't make or receive connections
*/
INACTIVE
}
/**
* The string for the boolean preference
* to use with the {@link SettingsManager} to enable or disable the plugin.
*/
String PREF_PLUGIN_ENABLE = "enable";
/**
* Reason flag returned by {@link #getReasonsDisabled()} to indicate that
* the plugin has been disabled by the user.
*/
int REASON_USER = 1;
/** /**
* Returns the plugin's transport identifier. * Returns the plugin's transport identifier.
*/ */
@@ -35,9 +78,18 @@ public interface Plugin {
void stop() throws PluginException; void stop() throws PluginException;
/** /**
* Returns true if the plugin is running. * Returns the current state of the plugin.
*/ */
boolean isRunning(); State getState();
/**
* Returns a set of flags indicating why the plugin is
* {@link State#DISABLED disabled}, or 0 if the plugin is not disabled.
* <p>
* The flags used are plugin-specific, except the generic flag
* {@link #REASON_USER}, which may be used by any plugin.
*/
int getReasonsDisabled();
/** /**
* Returns true if the plugin should be polled periodically to attempt to * Returns true if the plugin should be polled periodically to attempt to

View File

@@ -1,6 +1,10 @@
package org.briarproject.bramble.api.plugin; package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -32,12 +36,17 @@ public interface PluginCallback extends ConnectionHandler {
void mergeLocalProperties(TransportProperties p); void mergeLocalProperties(TransportProperties p);
/** /**
* Signals that the transport is enabled. * Informs the callback of the plugin's current state.
* <p>
* If the current state is different from the previous state, the callback
* will broadcast a {@link TransportStateEvent}. If the current state is
* {@link State#ACTIVE} and the previous state was not
* {@link State#ACTIVE}, the callback will broadcast a
* {@link TransportActiveEvent}. If the current state is not
* {@link State#ACTIVE} and the previous state was {@link State#ACTIVE},
* the callback will broadcast a {@link TransportInactiveEvent}.
* <p>
* This method can safely be called while holding a lock.
*/ */
void transportEnabled(); void pluginStateChanged(State state);
/**
* Signals that the transport is disabled.
*/
void transportDisabled();
} }

View File

@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map;
@NotNullByDefault @NotNullByDefault
public interface PluginConfig { public interface PluginConfig {
@@ -14,4 +16,11 @@ public interface PluginConfig {
Collection<SimplexPluginFactory> getSimplexFactories(); Collection<SimplexPluginFactory> getSimplexFactories();
boolean shouldPoll(); boolean shouldPoll();
/**
* Returns a map representing transport preferences. For each entry in the
* map, connections via the transports identified by the value are
* preferred to connections via the transport identified by the key.
*/
Map<TransportId, List<TransportId>> getTransportPreferences();
} }

View File

@@ -41,4 +41,17 @@ public interface PluginManager {
* Returns any duplex plugins that support rendezvous. * Returns any duplex plugins that support rendezvous.
*/ */
Collection<DuplexPlugin> getRendezvousPlugins(); Collection<DuplexPlugin> getRendezvousPlugins();
/**
* Enables or disables the plugin
* identified by the given {@link TransportId}.
* <p>
* Note that this applies the change asynchronously
* and there are no order guarantees.
* <p>
* If no plugin with the given {@link TransportId} is registered,
* this is a no-op.
*/
void setPluginEnabled(TransportId t, boolean enabled);
} }

View File

@@ -4,6 +4,7 @@ public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor"); TransportId ID = new TransportId("org.briarproject.bramble.tor");
// Transport properties
String PROP_ONION_V2 = "onion"; String PROP_ONION_V2 = "onion";
String PROP_ONION_V3 = "onion3"; String PROP_ONION_V3 = "onion3";
@@ -13,14 +14,37 @@ public interface TorConstants {
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
// Local settings (not shared with contacts)
String PREF_TOR_NETWORK = "network2"; String PREF_TOR_NETWORK = "network2";
String PREF_TOR_PORT = "port"; String PREF_TOR_PORT = "port";
String PREF_TOR_MOBILE = "useMobileData"; String PREF_TOR_MOBILE = "useMobileData";
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging"; String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
// Values for PREF_TOR_NETWORK
int PREF_TOR_NETWORK_AUTOMATIC = 0; int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1; int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2; int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3; int PREF_TOR_NETWORK_NEVER = 3;
// Default values for local settings
boolean DEFAULT_PREF_PLUGIN_ENABLE = true;
int DEFAULT_PREF_TOR_NETWORK = PREF_TOR_NETWORK_AUTOMATIC;
boolean DEFAULT_PREF_TOR_MOBILE = true;
boolean DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING = false;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_BATTERY = 2;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_MOBILE_DATA = 4;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_COUNTRY_BLOCKED = 8;
} }

View File

@@ -4,4 +4,7 @@ public interface WanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.wan"); TransportId ID = new TransportId("org.briarproject.bramble.wan");
// Default value for PREF_PLUGIN_ENABLE
boolean DEFAULT_PREF_PLUGIN_ENABLE = false;
} }

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.Plugin;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -14,6 +15,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractDuplexTransportConnection public abstract class AbstractDuplexTransportConnection
implements DuplexTransportConnection { implements DuplexTransportConnection {
protected final TransportProperties remote = new TransportProperties();
private final Plugin plugin; private final Plugin plugin;
private final Reader reader; private final Reader reader;
private final Writer writer; private final Writer writer;
@@ -44,6 +47,11 @@ public abstract class AbstractDuplexTransportConnection
return writer; return writer;
} }
@Override
public TransportProperties getRemoteProperties() {
return remote;
}
private class Reader implements TransportConnectionReader { private class Reader implements TransportConnectionReader {
@Override @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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; 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 * An interface for reading and writing data over a duplex transport. The
@@ -23,4 +24,10 @@ public interface DuplexTransportConnection {
* for writing to the connection. * for writing to the connection.
*/ */
TransportConnectionWriter getWriter(); TransportConnectionWriter getWriter();
/**
* Returns a possibly empty set of {@link TransportProperties} describing
* the remote peer.
*/
TransportProperties getRemoteProperties();
} }

View File

@@ -13,13 +13,14 @@ public class ConnectionClosedEvent extends Event {
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final boolean incoming; private final boolean incoming, exception;
public ConnectionClosedEvent(ContactId contactId, TransportId transportId, public ConnectionClosedEvent(ContactId contactId, TransportId transportId,
boolean incoming) { boolean incoming, boolean exception) {
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.incoming = incoming; this.incoming = incoming;
this.exception = exception;
} }
public ContactId getContactId() { public ContactId getContactId() {
@@ -33,4 +34,8 @@ public class ConnectionClosedEvent extends Event {
public boolean isIncoming() { public boolean isIncoming() {
return incoming; return incoming;
} }
public boolean isException() {
return exception;
}
} }

View File

@@ -2,20 +2,22 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a transport is disabled. * An event that is broadcast when a plugin enters the {@link State#ACTIVE}
* state.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportDisabledEvent extends Event { public class TransportActiveEvent extends Event {
private final TransportId transportId; private final TransportId transportId;
public TransportDisabledEvent(TransportId transportId) { public TransportActiveEvent(TransportId transportId) {
this.transportId = transportId; this.transportId = transportId;
} }

View File

@@ -2,20 +2,22 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a transport is enabled. * An event that is broadcast when a plugin leaves the {@link State#ACTIVE}
* state.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class TransportEnabledEvent extends Event { public class TransportInactiveEvent extends Event {
private final TransportId transportId; private final TransportId transportId;
public TransportEnabledEvent(TransportId transportId) { public TransportInactiveEvent(TransportId transportId) {
this.transportId = transportId; this.transportId = transportId;
} }

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the {@link State state} of a plugin changes.
*/
@Immutable
@NotNullByDefault
public class TransportStateEvent extends Event {
private final TransportId transportId;
private final State state;
public TransportStateEvent(TransportId transportId, State state) {
this.transportId = transportId;
this.state = state;
}
public TransportId getTransportId() {
return transportId;
}
public State getState() {
return state;
}
}

View File

@@ -11,4 +11,28 @@ public interface TransportPropertyConstants {
* The maximum length of a property's key or value in UTF-8 bytes. * The maximum length of a property's key or value in UTF-8 bytes.
*/ */
int MAX_PROPERTY_LENGTH = 100; 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, void addRemoteProperties(Transaction txn, ContactId c,
Map<TransportId, TransportProperties> props) throws DbException; 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. * Returns the local transport properties for all transports.
*/ */

View File

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

View File

@@ -1,8 +1,14 @@
package org.briarproject.bramble.api.sync; 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.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@Immutable
@NotNullByDefault
public class Message { public class Message {
/** /**

View File

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

View File

@@ -0,0 +1,23 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* A record containing a nonce for choosing between redundant sessions.
*/
@Immutable
@NotNullByDefault
public class Priority {
private final byte[] nonce;
public Priority(byte[] nonce) {
this.nonce = nonce;
}
public byte[] getNonce() {
return nonce;
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* An interface for handling a {@link Priority} record received by an
* incoming {@link SyncSession}.
*/
@NotNullByDefault
public interface PriorityHandler {
void handle(Priority p);
}

View File

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

View File

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

View File

@@ -2,6 +2,9 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.UniqueId; 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; import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
public interface SyncConstants { public interface SyncConstants {
@@ -11,6 +14,11 @@ public interface SyncConstants {
*/ */
byte PROTOCOL_VERSION = 0; 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. * The maximum length of a group descriptor in bytes.
*/ */
@@ -35,4 +43,16 @@ public interface SyncConstants {
* The maximum number of message IDs in an ack, offer or request record. * The maximum number of message IDs in an ack, offer or request record.
*/ */
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH; 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;
/**
* The length of the priority nonce used for choosing between redundant
* connections.
*/
int PRIORITY_NONCE_BYTES = 16;
} }

View File

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

View File

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

View File

@@ -2,18 +2,23 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.InputStream; import java.io.InputStream;
import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface SyncSessionFactory { public interface SyncSessionFactory {
SyncSession createIncomingSession(ContactId c, InputStream in); SyncSession createIncomingSession(ContactId c, InputStream in,
PriorityHandler handler);
SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency, SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
StreamWriter streamWriter); int maxLatency, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency, SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxIdleTime, StreamWriter streamWriter); int maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority);
} }

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); 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,13 +2,17 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.net.Inet6Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.isValidMac;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@NotNullByDefault @NotNullByDefault
public class PrivacyUtils { public class PrivacyUtils {
@@ -19,7 +23,7 @@ public class PrivacyUtils {
@Nullable @Nullable
public static String scrubMacAddress(@Nullable String address) { public static String scrubMacAddress(@Nullable String address) {
if (address == null || address.length() == 0) return null; if (isNullOrEmpty(address) || !isValidMac(address)) return address;
// this is a fake address we need to know about // this is a fake address we need to know about
if (address.equals("02:00:00:00:00:00")) return address; if (address.equals("02:00:00:00:00:00")) return address;
// keep first and last octet of MAC address // keep first and last octet of MAC address
@@ -27,39 +31,37 @@ public class PrivacyUtils {
+ address.substring(14, 17); + address.substring(14, 17);
} }
@Nullable
public static String scrubInetAddress(InetAddress address) { public static String scrubInetAddress(InetAddress address) {
// don't scrub link and site local addresses if (address instanceof Inet4Address) {
if (address.isLinkLocalAddress() || address.isSiteLocalAddress()) // Don't scrub local IPv4 addresses
return address.toString(); if (address.isLoopbackAddress() || address.isLinkLocalAddress() ||
// completely scrub IPv6 addresses address.isSiteLocalAddress()) {
if (address instanceof Inet6Address) return "[scrubbed]"; return address.getHostAddress();
// keep first and last octet of IPv4 addresses }
return scrubInetAddress(address.toString()); // Keep first and last octet of non-local IPv4 addresses
return scrubIpv4Address(address.getAddress());
} else {
// Keep first and last octet of IPv6 addresses
return scrubIpv6Address(address.getAddress());
}
} }
@Nullable private static String scrubIpv4Address(byte[] ipv4) {
public static String scrubInetAddress(@Nullable String address) { return (ipv4[0] & 0xFF) + ".[scrubbed]." + (ipv4[3] & 0xFF);
if (address == null) return null; }
int firstDot = address.indexOf("."); private static String scrubIpv6Address(byte[] ipv6) {
if (firstDot == -1) return "[scrubbed]"; String hex = toHexString(ipv6).toLowerCase();
String prefix = address.substring(0, firstDot + 1); return hex.substring(0, 2) + "[scrubbed]" + hex.substring(30);
int lastDot = address.lastIndexOf(".");
String suffix = address.substring(lastDot, address.length());
return prefix + "[scrubbed]" + suffix;
} }
@Nullable
public static String scrubSocketAddress(InetSocketAddress address) { public static String scrubSocketAddress(InetSocketAddress address) {
InetAddress inetAddress = address.getAddress(); return scrubInetAddress(address.getAddress());
return scrubInetAddress(inetAddress);
} }
@Nullable
public static String scrubSocketAddress(SocketAddress address) { public static String scrubSocketAddress(SocketAddress address) {
if (address instanceof InetSocketAddress) if (address instanceof InetSocketAddress)
return scrubSocketAddress((InetSocketAddress) address); return scrubSocketAddress((InetSocketAddress) address);
return scrubInetAddress(address.toString()); return "[scrubbed]";
} }
} }

View File

@@ -2,7 +2,7 @@ dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', '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.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', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', '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.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.3' implementation 'org.briarproject:jtorctl:0.3'
annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6 testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
@@ -26,7 +26,7 @@ dependencies {
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy: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' signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }

View File

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

View File

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

View File

@@ -85,14 +85,21 @@ class ClientHelperImpl implements ClientHelper {
@Override @Override
public void addLocalMessage(Message m, BdfDictionary metadata, public void addLocalMessage(Message m, BdfDictionary metadata,
boolean shared) throws DbException, FormatException { 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 @Override
public void addLocalMessage(Transaction txn, Message m, public void addLocalMessage(Transaction txn, Message m,
BdfDictionary metadata, boolean shared) BdfDictionary metadata, boolean shared, boolean temporary)
throws DbException, FormatException { 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 @Override

View File

@@ -0,0 +1,79 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.util.IoUtils.read;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
abstract class Connection {
protected static final Logger LOG = getLogger(Connection.class.getName());
final KeyManager keyManager;
final ConnectionRegistry connectionRegistry;
final StreamReaderFactory streamReaderFactory;
final StreamWriterFactory streamWriterFactory;
Connection(KeyManager keyManager, ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory) {
this.keyManager = keyManager;
this.connectionRegistry = connectionRegistry;
this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory;
}
@Nullable
StreamContext recogniseTag(TransportConnectionReader reader,
TransportId transportId) {
StreamContext ctx;
try {
byte[] tag = readTag(reader.getInputStream());
return keyManager.getStreamContext(transportId, tag);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
return null;
}
}
private byte[] readTag(InputStream in) throws IOException {
byte[] tag = new byte[TAG_LENGTH];
read(in, tag);
return tag;
}
void disposeOnError(TransportConnectionReader reader, boolean recognised) {
try {
reader.dispose(true, recognised);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
void disposeOnError(TransportConnectionWriter writer) {
try {
writer.dispose(true);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
}

View File

@@ -0,0 +1,114 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class ConnectionManagerImpl implements ConnectionManager {
private final Executor ioExecutor;
private final KeyManager keyManager;
private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory;
private final SyncSessionFactory syncSessionFactory;
private final HandshakeManager handshakeManager;
private final ContactExchangeManager contactExchangeManager;
private final ConnectionRegistry connectionRegistry;
private final TransportPropertyManager transportPropertyManager;
private final SecureRandom secureRandom;
@Inject
ConnectionManagerImpl(@IoExecutor Executor ioExecutor,
KeyManager keyManager, StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionRegistry connectionRegistry,
TransportPropertyManager transportPropertyManager,
SecureRandom secureRandom) {
this.ioExecutor = ioExecutor;
this.keyManager = keyManager;
this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory;
this.syncSessionFactory = syncSessionFactory;
this.handshakeManager = handshakeManager;
this.contactExchangeManager = contactExchangeManager;
this.connectionRegistry = connectionRegistry;
this.transportPropertyManager = transportPropertyManager;
this.secureRandom = secureRandom;
}
@Override
public void manageIncomingConnection(TransportId t,
TransportConnectionReader r) {
ioExecutor.execute(new IncomingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, t, r));
}
@Override
public void manageIncomingConnection(TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new IncomingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
t, d));
}
@Override
public void manageIncomingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new IncomingHandshakeConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
handshakeManager, contactExchangeManager, this, p, t, d));
}
@Override
public void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w));
}
@Override
public void manageOutgoingConnection(ContactId c, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new OutgoingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
secureRandom, c, t, d));
}
@Override
public void manageOutgoingConnection(PendingContactId p, TransportId t,
DuplexTransportConnection d) {
ioExecutor.execute(new OutgoingHandshakeConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
handshakeManager, contactExchangeManager, this, p, t, d));
}
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class ConnectionModule {
@Provides
ConnectionManager provideConnectionManager(
ConnectionManagerImpl connectionManager) {
return connectionManager;
}
@Provides
@Singleton
ConnectionRegistry provideConnectionRegistry(
ConnectionRegistryImpl connectionRegistry) {
return connectionRegistry;
}
}

View File

@@ -0,0 +1,283 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.sync.Priority;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
class ConnectionRegistryImpl implements ConnectionRegistry {
private static final Logger LOG =
getLogger(ConnectionRegistryImpl.class.getName());
private final EventBus eventBus;
private final Map<TransportId, List<TransportId>> transportPrefs;
private final Object lock = new Object();
@GuardedBy("lock")
private final Map<ContactId, List<ConnectionRecord>> contactConnections;
@GuardedBy("lock")
private final Set<PendingContactId> connectedPendingContacts;
@Inject
ConnectionRegistryImpl(EventBus eventBus, PluginConfig pluginConfig) {
this.eventBus = eventBus;
transportPrefs = pluginConfig.getTransportPreferences();
contactConnections = new HashMap<>();
connectedPendingContacts = new HashSet<>();
}
@Override
public void registerIncomingConnection(ContactId c, TransportId t,
InterruptibleConnection conn) {
registerConnection(c, t, conn, true);
}
@Override
public void registerOutgoingConnection(ContactId c, TransportId t,
InterruptibleConnection conn, Priority priority) {
registerConnection(c, t, conn, false);
setPriority(c, t, conn, priority);
}
private void registerConnection(ContactId c, TransportId t,
InterruptibleConnection conn, boolean incoming) {
if (LOG.isLoggable(INFO)) {
if (incoming) LOG.info("Incoming connection registered: " + t);
else LOG.info("Outgoing connection registered: " + t);
}
boolean firstConnection;
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null) {
recs = new ArrayList<>();
contactConnections.put(c, recs);
}
firstConnection = recs.isEmpty();
recs.add(new ConnectionRecord(t, conn));
}
eventBus.broadcast(new ConnectionOpenedEvent(c, t, incoming));
if (firstConnection) {
LOG.info("Contact connected");
eventBus.broadcast(new ContactConnectedEvent(c));
}
}
@Override
public void setPriority(ContactId c, TransportId t,
InterruptibleConnection conn, Priority priority) {
if (LOG.isLoggable(INFO)) LOG.info("Setting connection priority: " + t);
List<InterruptibleConnection> toInterrupt;
boolean interruptNewConnection = false;
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null) throw new IllegalArgumentException();
toInterrupt = new ArrayList<>(recs.size());
for (ConnectionRecord rec : recs) {
if (rec.conn == conn) {
// Store the priority of this connection
rec.priority = priority;
} else if (rec.priority != null) {
int compare = compareConnections(t, priority,
rec.transportId, rec.priority);
if (compare == -1) {
// The old connection is better than the new one
interruptNewConnection = true;
} else if (compare == 1 && !rec.interrupted) {
// The new connection is better than the old one
toInterrupt.add(rec.conn);
rec.interrupted = true;
}
}
}
}
if (interruptNewConnection) {
LOG.info("Interrupting new connection");
conn.interruptOutgoingSession();
}
for (InterruptibleConnection old : toInterrupt) {
LOG.info("Interrupting old connection");
old.interruptOutgoingSession();
}
}
private int compareConnections(TransportId tA, Priority pA, TransportId tB,
Priority pB) {
if (getBetterTransports(tA).contains(tB)) return -1;
if (getBetterTransports(tB).contains(tA)) return 1;
return tA.equals(tB) ? Bytes.compare(pA.getNonce(), pB.getNonce()) : 0;
}
private List<TransportId> getBetterTransports(TransportId t) {
List<TransportId> better = transportPrefs.get(t);
return better == null ? emptyList() : better;
}
@Override
public void unregisterConnection(ContactId c, TransportId t,
InterruptibleConnection conn, boolean incoming, boolean exception) {
if (LOG.isLoggable(INFO)) {
if (incoming) LOG.info("Incoming connection unregistered: " + t);
else LOG.info("Outgoing connection unregistered: " + t);
}
boolean lastConnection;
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null || !recs.remove(new ConnectionRecord(t, conn)))
throw new IllegalArgumentException();
lastConnection = recs.isEmpty();
}
eventBus.broadcast(
new ConnectionClosedEvent(c, t, incoming, exception));
if (lastConnection) {
LOG.info("Contact disconnected");
eventBus.broadcast(new ContactDisconnectedEvent(c));
}
}
@Override
public Collection<ContactId> getConnectedContacts(TransportId t) {
synchronized (lock) {
List<ContactId> contactIds = new ArrayList<>();
for (Entry<ContactId, List<ConnectionRecord>> e :
contactConnections.entrySet()) {
for (ConnectionRecord rec : e.getValue()) {
if (rec.transportId.equals(t)) {
contactIds.add(e.getKey());
break;
}
}
}
if (LOG.isLoggable(INFO)) {
LOG.info(contactIds.size() + " contacts connected: " + t);
}
return contactIds;
}
}
@Override
public Collection<ContactId> getConnectedOrBetterContacts(TransportId t) {
synchronized (lock) {
List<TransportId> better = getBetterTransports(t);
List<ContactId> contactIds = new ArrayList<>();
for (Entry<ContactId, List<ConnectionRecord>> e :
contactConnections.entrySet()) {
for (ConnectionRecord rec : e.getValue()) {
if (rec.transportId.equals(t) ||
better.contains(rec.transportId)) {
contactIds.add(e.getKey());
break;
}
}
}
if (LOG.isLoggable(INFO)) {
LOG.info(contactIds.size()
+ " contacts connected or better: " + t);
}
return contactIds;
}
}
@Override
public boolean isConnected(ContactId c, TransportId t) {
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
if (recs == null) return false;
for (ConnectionRecord rec : recs) {
if (rec.transportId.equals(t)) return true;
}
return false;
}
}
@Override
public boolean isConnected(ContactId c) {
synchronized (lock) {
List<ConnectionRecord> recs = contactConnections.get(c);
return recs != null && !recs.isEmpty();
}
}
@Override
public boolean registerConnection(PendingContactId p) {
boolean added;
synchronized (lock) {
added = connectedPendingContacts.add(p);
}
if (added) eventBus.broadcast(new RendezvousConnectionOpenedEvent(p));
return added;
}
@Override
public void unregisterConnection(PendingContactId p, boolean success) {
synchronized (lock) {
if (!connectedPendingContacts.remove(p))
throw new IllegalArgumentException();
}
eventBus.broadcast(new RendezvousConnectionClosedEvent(p, success));
}
private static class ConnectionRecord {
private final TransportId transportId;
private final InterruptibleConnection conn;
@GuardedBy("lock")
@Nullable
private Priority priority = null;
@GuardedBy("lock")
private boolean interrupted = false;
private ConnectionRecord(TransportId transportId,
InterruptibleConnection conn) {
this.transportId = transportId;
this.conn = conn;
}
@Override
public boolean equals(Object o) {
if (o instanceof ConnectionRecord) {
return conn == ((ConnectionRecord) o).conn;
} else {
return false;
}
}
@Override
public int hashCode() {
return conn.hashCode();
}
}
}

View File

@@ -0,0 +1,109 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@NotNullByDefault
abstract class DuplexSyncConnection extends SyncConnection
implements InterruptibleConnection {
final Executor ioExecutor;
final TransportId transportId;
final TransportConnectionReader reader;
final TransportConnectionWriter writer;
final TransportProperties remote;
private final Object interruptLock = new Object();
@GuardedBy("interruptLock")
@Nullable
private SyncSession outgoingSession = null;
@GuardedBy("interruptLock")
private boolean interruptWaiting = false;
@Override
public void interruptOutgoingSession() {
SyncSession out = null;
synchronized (interruptLock) {
if (outgoingSession == null) interruptWaiting = true;
else out = outgoingSession;
}
if (out != null) out.interrupt();
}
void setOutgoingSession(SyncSession outgoingSession) {
boolean interruptWasWaiting = false;
synchronized (interruptLock) {
this.outgoingSession = outgoingSession;
if (interruptWaiting) {
interruptWasWaiting = true;
interruptWaiting = false;
}
}
if (interruptWasWaiting) outgoingSession.interrupt();
}
DuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, TransportId transportId,
DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.ioExecutor = ioExecutor;
this.transportId = transportId;
reader = connection.getReader();
writer = connection.getWriter();
remote = connection.getRemoteProperties();
}
void onReadError(boolean recognised) {
disposeOnError(reader, recognised);
disposeOnError(writer);
interruptOutgoingSession();
}
void onWriteError() {
disposeOnError(reader, true);
disposeOnError(writer);
}
SyncSession createDuplexOutgoingSession(StreamContext ctx,
TransportConnectionWriter w, @Nullable Priority priority)
throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createDuplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), w.getMaxIdleTime(),
streamWriter, priority);
}
}

View File

@@ -0,0 +1,72 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
abstract class HandshakeConnection extends Connection {
final HandshakeManager handshakeManager;
final ContactExchangeManager contactExchangeManager;
final ConnectionManager connectionManager;
final PendingContactId pendingContactId;
final TransportId transportId;
final DuplexTransportConnection connection;
final TransportConnectionReader reader;
final TransportConnectionWriter writer;
HandshakeConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionManager connectionManager,
PendingContactId pendingContactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory);
this.handshakeManager = handshakeManager;
this.contactExchangeManager = contactExchangeManager;
this.connectionManager = connectionManager;
this.pendingContactId = pendingContactId;
this.transportId = transportId;
this.connection = connection;
reader = connection.getReader();
writer = connection.getWriter();
}
@Nullable
StreamContext allocateStreamContext(PendingContactId pendingContactId,
TransportId transportId) {
try {
return keyManager.getStreamContext(pendingContactId, transportId);
} catch (DbException e) {
logException(LOG, WARNING, e);
return null;
}
}
void onError(boolean recognised) {
disposeOnError(reader, recognised);
disposeOnError(writer);
}
}

View File

@@ -0,0 +1,107 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.util.concurrent.Executor;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
IncomingDuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, TransportId transportId,
DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
}
@Override
public void run() {
// Read and recognise the tag
StreamContext ctx = recogniseTag(reader, transportId);
if (ctx == null) {
LOG.info("Unrecognised tag");
onReadError(false);
return;
}
ContactId contactId = ctx.getContactId();
if (contactId == null) {
LOG.warning("Expected contact tag, got rendezvous tag");
onReadError(true);
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onReadError(true);
return;
}
connectionRegistry.registerIncomingConnection(contactId, transportId,
this);
// Start the outgoing session on another thread
ioExecutor.execute(() -> runOutgoingSession(contactId));
try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// Update the connection registry when we receive our priority
PriorityHandler handler = p -> connectionRegistry.setPriority(
contactId, transportId, this, p);
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
reader.dispose(false, true);
interruptOutgoingSession();
connectionRegistry.unregisterConnection(contactId, transportId,
this, true, false);
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
onReadError(true);
connectionRegistry.unregisterConnection(contactId, transportId,
this, true, true);
}
}
private void runOutgoingSession(ContactId contactId) {
// Allocate a stream context
StreamContext ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
try {
// Create and run the outgoing session
SyncSession out = createDuplexOutgoingSession(ctx, writer, null);
setOutgoingSession(out);
out.run();
writer.dispose(false);
} catch (IOException e) {
logException(LOG, WARNING, e);
onWriteError();
}
}
}

View File

@@ -0,0 +1,93 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingHandshakeConnection extends HandshakeConnection
implements Runnable {
IncomingHandshakeConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionManager connectionManager,
PendingContactId pendingContactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, handshakeManager, contactExchangeManager,
connectionManager, pendingContactId, transportId, connection);
}
@Override
public void run() {
// Read and recognise the tag
StreamContext ctxIn = recogniseTag(reader, transportId);
if (ctxIn == null) {
LOG.info("Unrecognised tag");
onError(false);
return;
}
PendingContactId inPendingContactId = ctxIn.getPendingContactId();
if (inPendingContactId == null) {
LOG.warning("Expected rendezvous tag, got contact tag");
onError(true);
return;
}
// Allocate the outgoing stream context
StreamContext ctxOut =
allocateStreamContext(pendingContactId, transportId);
if (ctxOut == null) {
LOG.warning("Could not allocate stream context");
onError(true);
return;
}
// Close the connection if it's redundant
if (!connectionRegistry.registerConnection(pendingContactId)) {
LOG.info("Redundant rendezvous connection");
onError(true);
return;
}
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
// Flush the output stream to send the outgoing stream header
StreamWriter out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
HandshakeResult result =
handshakeManager.handshake(pendingContactId, in, out);
contactExchangeManager.exchangeContacts(pendingContactId,
connection, result.getMasterKey(), result.isAlice(), false);
connectionRegistry.unregisterConnection(pendingContactId, true);
// Reuse the connection as a transport connection
connectionManager.manageIncomingConnection(transportId, connection);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError(true);
connectionRegistry.unregisterConnection(pendingContactId, false);
}
}
}

View File

@@ -0,0 +1,79 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingSimplexSyncConnection extends SyncConnection implements Runnable {
private final TransportId transportId;
private final TransportConnectionReader reader;
IncomingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
TransportId transportId, TransportConnectionReader reader) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.transportId = transportId;
this.reader = reader;
}
@Override
public void run() {
// Read and recognise the tag
StreamContext ctx = recogniseTag(reader, transportId);
if (ctx == null) {
LOG.info("Unrecognised tag");
onError(false);
return;
}
ContactId contactId = ctx.getContactId();
if (contactId == null) {
LOG.warning("Received rendezvous stream, expected contact");
onError(true);
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onError(true);
return;
}
try {
// We don't expect to receive a priority for this connection
PriorityHandler handler = p ->
LOG.info("Ignoring priority for simplex connection");
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
reader.dispose(false, true);
} catch (IOException e) {
logException(LOG, WARNING, e);
onError(true);
}
}
private void onError(boolean recognised) {
disposeOnError(reader, recognised);
}
}

View File

@@ -0,0 +1,140 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.SyncConstants.PRIORITY_NONCE_BYTES;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class OutgoingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
private final SecureRandom secureRandom;
private final ContactId contactId;
OutgoingDuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, SecureRandom secureRandom, ContactId contactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
this.secureRandom = secureRandom;
this.contactId = contactId;
}
@Override
public void run() {
// Allocate a stream context
StreamContext ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Cannot use handshake mode stream context");
onWriteError();
return;
}
// Start the incoming session on another thread
Priority priority = generatePriority();
ioExecutor.execute(() -> runIncomingSession(priority));
try {
// Create and run the outgoing session
SyncSession out =
createDuplexOutgoingSession(ctx, writer, priority);
setOutgoingSession(out);
out.run();
writer.dispose(false);
} catch (IOException e) {
logException(LOG, WARNING, e);
onWriteError();
}
}
private void runIncomingSession(Priority priority) {
// Read and recognise the tag
StreamContext ctx = recogniseTag(reader, transportId);
// Unrecognised tags are suspicious in this case
if (ctx == null) {
LOG.warning("Unrecognised tag for returning stream");
onReadError();
return;
}
// Check that the stream comes from the expected contact
ContactId inContactId = ctx.getContactId();
if (inContactId == null) {
LOG.warning("Expected contact tag, got rendezvous tag");
onReadError();
return;
}
if (!contactId.equals(inContactId)) {
LOG.warning("Wrong contact ID for returning stream");
onReadError();
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onReadError();
return;
}
connectionRegistry.registerOutgoingConnection(contactId, transportId,
this, priority);
try {
// Store any transport properties discovered from the connection
transportPropertyManager.addRemotePropertiesFromConnection(
contactId, transportId, remote);
// We don't expect to receive a priority for this connection
PriorityHandler handler = p ->
LOG.info("Ignoring priority for outgoing connection");
// Create and run the incoming session
createIncomingSession(ctx, reader, handler).run();
reader.dispose(false, true);
interruptOutgoingSession();
connectionRegistry.unregisterConnection(contactId, transportId,
this, false, false);
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
onReadError();
connectionRegistry.unregisterConnection(contactId, transportId,
this, false, true);
}
}
private void onReadError() {
// 'Recognised' is always true for outgoing connections
onReadError(true);
}
private Priority generatePriority() {
byte[] nonce = new byte[PRIORITY_NONCE_BYTES];
secureRandom.nextBytes(nonce);
return new Priority(nonce);
}
}

View File

@@ -0,0 +1,115 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class OutgoingHandshakeConnection extends HandshakeConnection
implements Runnable {
OutgoingHandshakeConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
HandshakeManager handshakeManager,
ContactExchangeManager contactExchangeManager,
ConnectionManager connectionManager,
PendingContactId pendingContactId,
TransportId transportId, DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, handshakeManager, contactExchangeManager,
connectionManager, pendingContactId, transportId, connection);
}
@Override
public void run() {
// Allocate the outgoing stream context
StreamContext ctxOut =
allocateStreamContext(pendingContactId, transportId);
if (ctxOut == null) {
LOG.warning("Could not allocate stream context");
onError();
return;
}
// Flush the output stream to send the outgoing stream header
StreamWriter out;
try {
out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
} catch (IOException e) {
logException(LOG, WARNING, e);
onError();
return;
}
// Read and recognise the tag
StreamContext ctxIn = recogniseTag(reader, transportId);
// Unrecognised tags are suspicious in this case
if (ctxIn == null) {
LOG.warning("Unrecognised tag for returning stream");
onError();
return;
}
// Check that the stream comes from the expected pending contact
PendingContactId inPendingContactId = ctxIn.getPendingContactId();
if (inPendingContactId == null) {
LOG.warning("Expected rendezvous tag, got contact tag");
onError();
return;
}
if (!inPendingContactId.equals(pendingContactId)) {
LOG.warning("Wrong pending contact ID for returning stream");
onError();
return;
}
// Close the connection if it's redundant
if (!connectionRegistry.registerConnection(pendingContactId)) {
LOG.info("Redundant rendezvous connection");
onError();
return;
}
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
HandshakeResult result =
handshakeManager.handshake(pendingContactId, in, out);
Contact contact = contactExchangeManager.exchangeContacts(
pendingContactId, connection, result.getMasterKey(),
result.isAlice(), false);
connectionRegistry.unregisterConnection(pendingContactId, true);
// Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contact.getId(),
transportId, connection);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onError();
connectionRegistry.unregisterConnection(pendingContactId, false);
}
}
private void onError() {
// 'Recognised' is always true for outgoing connections
onError(true);
}
}

View File

@@ -0,0 +1,78 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
private final ContactId contactId;
private final TransportId transportId;
private final TransportConnectionWriter writer;
OutgoingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
ContactId contactId, TransportId transportId,
TransportConnectionWriter writer) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.contactId = contactId;
this.transportId = transportId;
this.writer = writer;
}
@Override
public void run() {
// Allocate a stream context
StreamContext ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onError();
return;
}
try {
// Create and run the outgoing session
createSimplexOutgoingSession(ctx, writer).run();
writer.dispose(false);
} catch (IOException e) {
logException(LOG, WARNING, e);
onError();
}
}
private void onError() {
disposeOnError(writer);
}
private SyncSession createSimplexOutgoingSession(StreamContext ctx,
TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), streamWriter);
}
}

View File

@@ -0,0 +1,64 @@
package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class SyncConnection extends Connection {
final SyncSessionFactory syncSessionFactory;
final TransportPropertyManager transportPropertyManager;
SyncConnection(KeyManager keyManager, ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory);
this.syncSessionFactory = syncSessionFactory;
this.transportPropertyManager = transportPropertyManager;
}
@Nullable
StreamContext allocateStreamContext(ContactId contactId,
TransportId transportId) {
try {
return keyManager.getStreamContext(contactId, transportId);
} catch (DbException e) {
logException(LOG, WARNING, e);
return null;
}
}
SyncSession createIncomingSession(StreamContext ctx,
TransportConnectionReader r, PriorityHandler handler)
throws IOException {
InputStream streamReader = streamReaderFactory.createStreamReader(
r.getInputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory
.createIncomingSession(c, streamReader, handler);
}
}

View File

@@ -139,7 +139,8 @@ class ContactManagerImpl implements ContactManager, EventListener {
pendingContactFactory.createPendingContact(link, alias); pendingContactFactory.createPendingContact(link, alias);
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
db.addPendingContact(txn, p); AuthorId local = identityManager.getLocalAuthor(txn).getId();
db.addPendingContact(txn, p, local);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn); KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(), keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
ourKeyPair); ourKeyPair);
@@ -147,7 +148,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
states.put(p.getId(), WAITING_FOR_CONNECTION);
return p; 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.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; 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.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser; 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.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; 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 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_AGREEMENT;
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE; 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.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; 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 SIGNATURE_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits 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_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 SecureRandom secureRandom;
private final PasswordBasedKdf passwordBasedKdf; private final PasswordBasedKdf passwordBasedKdf;
@@ -311,7 +317,8 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public byte[] encryptWithPassword(byte[] input, String password) { public byte[] encryptWithPassword(byte[] input, String password,
@Nullable KeyStrengthener keyStrengthener) {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher(); AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes(); int macBytes = cipher.getMacBytes();
// Generate a random salt // Generate a random salt
@@ -319,8 +326,9 @@ class CryptoComponentImpl implements CryptoComponent {
secureRandom.nextBytes(salt); secureRandom.nextBytes(salt);
// Calibrate the KDF // Calibrate the KDF
int cost = passwordBasedKdf.chooseCostParameter(); int cost = passwordBasedKdf.chooseCostParameter();
// Derive the key from the password // Derive the encryption key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost); SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
if (keyStrengthener != null) key = keyStrengthener.strengthenKey(key);
// Generate a random IV // Generate a random IV
byte[] iv = new byte[STORAGE_IV_BYTES]; byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv); secureRandom.nextBytes(iv);
@@ -331,7 +339,9 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] output = new byte[outputLen]; byte[] output = new byte[outputLen];
int outputOff = 0; int outputOff = 0;
// Format version // Format version
output[outputOff] = PBKDF_FORMAT_SCRYPT; byte formatVersion = keyStrengthener == null
? PBKDF_FORMAT_SCRYPT : PBKDF_FORMAT_SCRYPT_STRENGTHENED;
output[outputOff] = formatVersion;
outputOff++; outputOff++;
// Salt // Salt
arraycopy(salt, 0, output, outputOff, salt.length); arraycopy(salt, 0, output, outputOff, salt.length);
@@ -353,21 +363,26 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @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(); AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes(); int macBytes = cipher.getMacBytes();
// The input contains the format version, salt, cost parameter, IV, // The input contains the format version, salt, cost parameter, IV,
// ciphertext and MAC // ciphertext and MAC
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes) + STORAGE_IV_BYTES + macBytes) {
return null; // Invalid input throw new DecryptionException(INVALID_CIPHERTEXT);
}
int inputOff = 0; int inputOff = 0;
// Format version // Format version
byte formatVersion = input[inputOff]; byte formatVersion = input[inputOff];
inputOff++; inputOff++;
if (formatVersion != PBKDF_FORMAT_SCRYPT) // Check whether we support this format version
return null; // Unknown format if (formatVersion != PBKDF_FORMAT_SCRYPT &&
formatVersion != PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
// Salt // Salt
byte[] salt = new byte[PBKDF_SALT_BYTES]; byte[] salt = new byte[PBKDF_SALT_BYTES];
arraycopy(input, inputOff, salt, 0, salt.length); arraycopy(input, inputOff, salt, 0, salt.length);
@@ -375,14 +390,22 @@ class CryptoComponentImpl implements CryptoComponent {
// Cost parameter // Cost parameter
long cost = ByteUtils.readUint32(input, inputOff); long cost = ByteUtils.readUint32(input, inputOff);
inputOff += INT_32_BYTES; inputOff += INT_32_BYTES;
if (cost < 2 || cost > Integer.MAX_VALUE) if (cost < 2 || cost > Integer.MAX_VALUE) {
return null; // Invalid cost parameter throw new DecryptionException(INVALID_CIPHERTEXT);
}
// IV // IV
byte[] iv = new byte[STORAGE_IV_BYTES]; byte[] iv = new byte[STORAGE_IV_BYTES];
arraycopy(input, inputOff, iv, 0, iv.length); arraycopy(input, inputOff, iv, 0, iv.length);
inputOff += 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); 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 // Initialise the cipher
try { try {
cipher.init(false, key, iv); cipher.init(false, key, iv);
@@ -396,10 +419,16 @@ class CryptoComponentImpl implements CryptoComponent {
cipher.process(input, inputOff, inputLen, output, 0); cipher.process(input, inputOff, inputLen, output, 0);
return output; return output;
} catch (GeneralSecurityException e) { } 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 @Override
public byte[] encryptToKey(PublicKey publicKey, byte[] plaintext) { public byte[] encryptToKey(PublicKey publicKey, byte[] plaintext) {
try { try {

View File

@@ -33,6 +33,7 @@ import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -115,7 +116,7 @@ interface Database<T> {
* if the message was created locally. * if the message was created locally.
*/ */
void addMessage(T txn, Message m, MessageState state, boolean shared, 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 * 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; 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. * Returns the group with the given ID.
* <p/> * <p/>
@@ -528,6 +539,13 @@ interface Database<T> {
*/ */
Settings getSettings(T txn, String namespace) throws DbException; 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. * Returns all transport keys for the given transport.
* <p/> * <p/>
@@ -630,6 +648,12 @@ interface Database<T> {
*/ */
void removePendingContact(T txn, PendingContactId p) throws DbException; 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. * 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, void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey,
PrivateKey privateKey) throws DbException; 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. * Marks the given message as shared.
*/ */
@@ -689,6 +718,12 @@ interface Database<T> {
void setReorderingWindow(T txn, KeySetId k, TransportId t, void setReorderingWindow(T txn, KeySetId k, TransportId t,
long timePeriod, long base, byte[] bitmap) throws DbException; 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. * 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.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent; 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.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.TransportKeySet; import org.briarproject.bramble.api.transport.TransportKeySet;
@@ -273,13 +274,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void addLocalMessage(Transaction transaction, Message m, 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(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsGroup(txn, m.getGroupId())) if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) { 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 MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
@@ -289,12 +291,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void addPendingContact(Transaction transaction, PendingContact p) public void addPendingContact(Transaction transaction, PendingContact p,
throws DbException { AuthorId local) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (db.containsPendingContact(txn, p.getId())) Contact contact = db.getContact(txn, p.getPublicKey(), local);
throw new PendingContactExistsException(); 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); db.addPendingContact(txn, p);
transaction.attach(new PendingContactAddedEvent(p)); transaction.attach(new PendingContactAddedEvent(p));
} }
@@ -715,6 +722,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getSettings(txn, namespace); 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 @Override
public Collection<TransportKeySet> getTransportKeys(Transaction transaction, public Collection<TransportKeySet> getTransportKeys(Transaction transaction,
TransportId t) throws DbException { TransportId t) throws DbException {
@@ -800,7 +816,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId()); db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId()); db.raiseAckFlag(txn, c, m.getId());
} else { } 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 MessageAddedEvent(m, c));
} }
transaction.attach(new MessageToAckEvent(c)); transaction.attach(new MessageToAckEvent(c));
@@ -908,6 +924,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new PendingContactRemovedEvent(p)); 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 @Override
public void removeTransport(Transaction transaction, TransportId t) public void removeTransport(Transaction transaction, TransportId t)
throws DbException { throws DbException {
@@ -967,6 +991,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); 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 @Override
public void setMessageShared(Transaction transaction, MessageId m) public void setMessageShared(Transaction transaction, MessageId m)
throws DbException { throws DbException {
@@ -975,8 +1009,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsMessage(txn, m)) if (!db.containsMessage(txn, m))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
if (db.getMessageState(txn, m) != DELIVERED) if (db.getMessageState(txn, m) != DELIVERED)
throw new IllegalArgumentException( throw new IllegalArgumentException("Shared undelivered message");
"Shared undelivered message");
db.setMessageShared(txn, m); db.setMessageShared(txn, m);
transaction.attach(new MessageSharedEvent(m)); transaction.attach(new MessageSharedEvent(m));
} }
@@ -1028,6 +1061,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap); 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 @Override
public void setTransportKeysActive(Transaction transaction, TransportId t, public void setTransportKeysActive(Transaction transaction, TransportId t,
KeySetId k) throws DbException { 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.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.IoUtils.isNonEmptyDirectory;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir; 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:"); LOG.info("Contents of account directory before opening DB:");
logFileOrDir(LOG, INFO, dir.getParentFile()); logFileOrDir(LOG, INFO, dir.getParentFile());
} }
boolean reopen = !dir.mkdirs(); boolean reopen = isNonEmptyDirectory(dir);
if (LOG.isLoggable(INFO)) LOG.info("Reopening DB: " + reopen); 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); super.open("org.h2.Driver", reopen, key, listener);
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Contents of account directory after opening DB:"); 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.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose; 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. * 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) public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException { throws DbException {
this.key = key; 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); super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener);
return reopen; return reopen;
} }

View File

@@ -62,6 +62,7 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import static java.sql.Types.BINARY; import static java.sql.Types.BINARY;
import static java.sql.Types.BOOLEAN; import static java.sql.Types.BOOLEAN;
@@ -97,7 +98,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
static final int CODE_SCHEMA_VERSION = 45; static final int CODE_SCHEMA_VERSION = 47;
// Time period offsets for incoming transport keys // Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1; private static final int OFFSET_PREV = -1;
@@ -134,6 +135,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " handshakePublicKey _BINARY," // Null if key is unknown + " handshakePublicKey _BINARY," // Null if key is unknown
+ " localAuthorId _HASH NOT NULL," + " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL," + " verified BOOLEAN NOT NULL,"
+ " syncVersions _BINARY DEFAULT '00' NOT NULL,"
+ " PRIMARY KEY (contactId)," + " PRIMARY KEY (contactId),"
+ " FOREIGN KEY (localAuthorId)" + " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)" + " REFERENCES localAuthors (authorId)"
@@ -177,6 +179,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " timestamp BIGINT NOT NULL," + " timestamp BIGINT NOT NULL,"
+ " state INT NOT NULL," + " state INT NOT NULL,"
+ " shared BOOLEAN NOT NULL," + " shared BOOLEAN NOT NULL,"
+ " temporary BOOLEAN NOT NULL,"
+ " length INT NOT NULL," + " length INT NOT NULL,"
+ " raw BLOB," // Null if message has been deleted + " raw BLOB," // Null if message has been deleted
+ " PRIMARY KEY (messageId)," + " PRIMARY KEY (messageId),"
@@ -336,25 +339,26 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final Logger LOG = private static final Logger LOG =
getLogger(JdbcDatabase.class.getName()); getLogger(JdbcDatabase.class.getName());
// Different database libraries use different names for certain types
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final Clock clock; private final Clock clock;
private final DatabaseTypes dbTypes; 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 final LinkedList<Connection> connections = new LinkedList<>();
private int openConnections = 0; // Locking: connectionsLock @GuardedBy("connectionsLock")
private boolean closed = false; // Locking: connectionsLock private int openConnections = 0;
@GuardedBy("connectionsLock")
private boolean closed = false;
protected abstract Connection createConnection() protected abstract Connection createConnection()
throws DbException, SQLException; throws DbException, SQLException;
protected abstract void compactAndClose() throws DbException; protected abstract void compactAndClose() throws DbException;
private final Lock connectionsLock = new ReentrantLock();
private final Condition connectionsChanged = connectionsLock.newCondition();
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory, JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
Clock clock) { Clock clock) {
this.dbTypes = databaseTypes; this.dbTypes = databaseTypes;
@@ -457,7 +461,9 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration41_42(dbTypes), new Migration41_42(dbTypes),
new Migration42_43(dbTypes), new Migration42_43(dbTypes),
new Migration43_44(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 @Override
public void addMessage(Connection txn, Message m, MessageState state, public void addMessage(Connection txn, Message m, MessageState state,
boolean messageShared, @Nullable ContactId sender) boolean shared, boolean temporary, @Nullable ContactId sender)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO messages (messageId, groupId, timestamp," String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
+ " state, shared, length, raw)" + " state, shared, temporary, length, raw)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getId().getBytes()); ps.setBytes(1, m.getId().getBytes());
ps.setBytes(2, m.getGroupId().getBytes()); ps.setBytes(2, m.getGroupId().getBytes());
ps.setLong(3, m.getTimestamp()); ps.setLong(3, m.getTimestamp());
ps.setInt(4, state.getValue()); ps.setInt(4, state.getValue());
ps.setBoolean(5, messageShared); ps.setBoolean(5, shared);
ps.setBoolean(6, temporary);
byte[] raw = messageFactory.getRawMessage(m); byte[] raw = messageFactory.getRawMessage(m);
ps.setInt(6, raw.length); ps.setInt(7, raw.length);
ps.setBytes(7, raw); ps.setBytes(8, raw);
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -804,8 +811,7 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean offered = removeOfferedMessage(txn, c, m.getId()); boolean offered = removeOfferedMessage(txn, c, m.getId());
boolean seen = offered || c.equals(sender); boolean seen = offered || c.equals(sender);
addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(), addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(),
raw.length, state, e.getValue(), messageShared, raw.length, state, e.getValue(), shared, false, seen);
false, seen);
} }
// Update denormalised column in messageDependencies if dependency // Update denormalised column in messageDependencies if dependency
// is in same group as dependent // 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 @Override
public Group getGroup(Connection txn, GroupId g) throws DbException { public Group getGroup(Connection txn, GroupId g) throws DbException {
PreparedStatement ps = null; 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 @Override
public Collection<TransportKeySet> getTransportKeys(Connection txn, public Collection<TransportKeySet> getTransportKeys(Connection txn,
TransportId t) throws DbException { 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 @Override
public void removeTransport(Connection txn, TransportId t) public void removeTransport(Connection txn, TransportId t)
throws DbException { 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 @Override
public void setMessageShared(Connection txn, MessageId m) public void setMessageShared(Connection txn, MessageId m)
throws DbException { 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 @Override
public void setTransportKeysActive(Connection txn, TransportId t, public void setTransportKeysActive(Connection txn, TransportId t,
KeySetId k) throws DbException { 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);
}
}
}

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