Compare commits

..

53 Commits

Author SHA1 Message Date
akwizgran
4fcce7116c Decouple poller from plugin manager. 2019-05-14 17:52:44 +01:00
Torsten Grote
421ca309c7 Merge branch '1538-create-handshake-key-pair' into 'master'
Generate and store handshake key pair at startup if necessary

Closes #1538

See merge request briar/briar!1082
2019-05-14 15:39:44 +00:00
akwizgran
43787deafd Address review comments. 2019-05-14 15:55:42 +01:00
Torsten Grote
451edba467 Merge branch 'live-event-reduce-visibility' into 'master'
Reduce visibility of LiveEvent inner classes

See merge request briar/briar!1092
2019-05-10 16:52:49 +00:00
Torsten Grote
5880479987 Merge branch '1537-contact-manager-pending-contacts' into 'master'
Implement contact manager methods for pending contacts

Closes #1537

See merge request briar/briar!1081
2019-05-10 15:06:08 +00:00
akwizgran
71d8fb2083 Add unit tests for Base32 encoding and decoding. 2019-05-10 15:33:19 +01:00
akwizgran
0825e77dd7 Static import. 2019-05-10 15:11:44 +01:00
akwizgran
593a709a7f Remove redundant regex. 2019-05-10 15:06:12 +01:00
akwizgran
322fefb2a2 Use matcher to discard prefix if present. 2019-05-10 15:02:47 +01:00
akwizgran
8005cdc659 Reduce visibility of LiveEvent inner classes. 2019-05-10 10:20:49 +01:00
akwizgran
33fdca4aa1 Merge branch 'live-event' into 'master'
Migrate existing uses of event-like LiveData to LiveEvent

See merge request briar/briar!1090
2019-05-10 09:09:33 +00:00
akwizgran
e5fc91b620 Rename exceptions and events. 2019-05-10 10:02:49 +01:00
akwizgran
9c08073e49 Rename account to identity. 2019-05-10 10:02:49 +01:00
akwizgran
5553b7d0e4 Remove unused method. 2019-05-10 10:02:48 +01:00
akwizgran
2cce0f5fe2 Remove OpenDatabaseHook priorities. 2019-05-10 10:02:48 +01:00
akwizgran
ebae1037be Remove unnecessary null check. 2019-05-10 10:02:48 +01:00
akwizgran
0c99ef0e5b Clean up some duplicated code. 2019-05-10 10:02:48 +01:00
akwizgran
faba9a6b70 Generate handshake keys on demand, store when DB is opened. 2019-05-10 10:02:47 +01:00
akwizgran
891c82b2e5 Add javadocs to DB hook interfaces. 2019-05-10 10:02:47 +01:00
akwizgran
56fbc93962 Move handshake keys from LocalAuthor to Account. 2019-05-10 10:02:47 +01:00
akwizgran
251eb9e712 Add javadoc for handshakeKeys flag. 2019-05-10 10:02:47 +01:00
akwizgran
8b2b7599f9 Generate and store handshake keys at startup if needed. 2019-05-10 10:02:46 +01:00
akwizgran
8c315382e2 Add DB method for setting local handshake key pair. 2019-05-10 10:02:46 +01:00
akwizgran
8183a48ebb Add unit test for OpenDatabaseHook priority. 2019-05-10 10:02:45 +01:00
akwizgran
f6611daf7b Replace Client interface with OpenDatabaseHook. 2019-05-10 10:02:45 +01:00
akwizgran
00bc8ac768 Include handshake keys when loading all local authors. 2019-05-10 10:02:45 +01:00
akwizgran
75776eb7de Generate handshake keys when creating local author. 2019-05-10 10:02:45 +01:00
akwizgran
f0a3130bf3 Test that UnsupportedVersionException is thrown. 2019-05-10 10:01:32 +01:00
akwizgran
64aa121c9c Reuse UnsupportedVersionException for handshake links. 2019-05-10 10:01:22 +01:00
akwizgran
cc3486df94 Move UnsupportedVersionException to bramble.api package. 2019-05-10 10:01:06 +01:00
akwizgran
cd24be7e42 Add unit tests for pending contact factory. 2019-05-10 10:01:05 +01:00
akwizgran
fa562b40bc Implement contact manager methods for pending contacts. 2019-05-10 10:01:03 +01:00
akwizgran
fc8ca872a8 Add base32 encoder/decoder. 2019-05-10 09:59:16 +01:00
Torsten Grote
5b63eab314 [android] migrate existing uses of event-like LiveData to LiveEvent 2019-05-09 14:47:16 -03:00
akwizgran
6f0ab8b688 Merge branch '1234-remote-contacts' into 'master'
Implement UX for adding contacts remotely

Closes #1234

See merge request briar/briar!1035
2019-05-09 16:52:24 +00:00
Torsten Grote
dfc567cbfd [bramble] Remove PendingContact test code from ContactManagerImpl 2019-05-09 13:36:21 -03:00
Torsten Grote
de98a4cb12 [android] Introduce a (Mutable)LiveEvent for single-use LiveData 2019-05-09 13:20:09 -03:00
Torsten Grote
fbe375cc4e Use event instead of CommitAction to handle removed PendingContacts 2019-05-09 11:43:22 -03:00
Torsten Grote
19bc73ac61 [android] show Toast when user shares own handshake link
This also limits the AddContactActivity to run within one single task
2019-05-03 11:47:51 -03:00
Torsten Grote
d17331b578 [android] Set handshake link when received via sharing intent or link click 2019-05-03 10:18:47 -03:00
Torsten Grote
bec1f117ba Remote Contact Adding: Rename methods and add more exception handling 2019-05-03 09:48:20 -03:00
Torsten Grote
2c014b4e46 Only remove PendingContact from UI when removed from DB 2019-05-03 09:48:19 -03:00
Torsten Grote
7a71d2bad4 Remote Contact Adding UI: Address review comments 2019-05-03 09:48:19 -03:00
Torsten Grote
4bf21b2f3b [android] hide feature to add contacts remotely behind feature flag 2019-05-03 09:48:19 -03:00
Torsten Grote
4a57939b80 [android] finalize list of pending contacts and add test code 2019-05-03 09:48:19 -03:00
Torsten Grote
66cdf4f595 Refactored IntroductionSucceededEvent into more generic ContactAddedRemotelyEvent 2019-05-03 09:48:18 -03:00
Torsten Grote
3384477499 [android] Add BriarSnackbarBuilder to standardize snackbar creation 2019-05-03 09:48:18 -03:00
Torsten Grote
58ffc6e761 [android] rough sketch of UI for adding contacts remotely 2019-05-03 09:48:15 -03:00
akwizgran
df5ac59fc9 Merge branch 'gradle-android-3.4' into 'master'
Update the Android gradle plugin to version 3.4.0

See merge request briar/briar!1085
2019-05-03 09:36:46 +00:00
akwizgran
dc649b195a Merge branch '1552-send-controller-exception' into 'master'
Don't disable TextInputView directly, use controller

Closes #1552

See merge request briar/briar!1086
2019-05-01 09:38:34 +00:00
Torsten Grote
3d9a8f9bf8 [android] Use TextSendController to disable TextInputView
Fixes #1552
2019-04-26 13:45:49 -03:00
Torsten Grote
96975e0d43 Upgrade dagger, mockito and okhttp 2019-04-26 09:07:26 -03:00
Torsten Grote
6691e708e4 Update the Android gradle plugin to version 3.4.0 2019-04-25 20:55:30 -03:00
216 changed files with 4874 additions and 2048 deletions

View File

@@ -39,31 +39,6 @@
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
<XML> <XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML> </XML>

View File

@@ -33,7 +33,7 @@ dependencies {
tor 'org.briarproject:tor-android:0.3.5.8@zip' tor 'org.briarproject:tor-android:0.3.5.8@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.9@zip' tor 'org.briarproject:obfs4proxy-android:0.0.9@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'

View File

@@ -1,45 +1,45 @@
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.3.2:protos-26.3.2.jar:50238fb4298b297217b184b9cd93c14f83536fcee829eb0ca850bdb5b53251f0', 'com.android.tools.analytics-library:protos:26.4.0:protos-26.4.0.jar:ad760915586797d39319f402837b378bff3bb4ed583e3e0c48c965631fb2135f',
'com.android.tools.analytics-library:shared:26.3.2:shared-26.3.2.jar:ddd80dcf21905018b7c0583ba72b7282f446084d4952422609a09fbf8237ef71', 'com.android.tools.analytics-library:shared:26.4.0:shared-26.4.0.jar:1332106a905d48909c81268c9e414946de3e83487db394c6073b0a9b5c3d0ed2',
'com.android.tools.analytics-library:tracker:26.3.2:tracker-26.3.2.jar:28c575d2d1af003e96d7b375a668ee10b2673a2dd0f6438750aa8c3b42e7d0ad', 'com.android.tools.analytics-library:tracker:26.4.0:tracker-26.4.0.jar:d0020cfbfd4cd75935f2972d6a24089840d4a10df6f3ef2a796093217dd37796',
'com.android.tools.build:apksig:3.3.2:apksig-3.3.2.jar:84c4aaa20127c6c1fe6bdd334b3f5df71f54ad080be9029c8a10f43b6a908acd', 'com.android.tools.build:apksig:3.4.0:apksig-3.4.0.jar:91d5a1866139c69756280355a6f61b4d619d0516841580114f45a10f2177327e',
'com.android.tools.build:apkzlib:3.3.2:apkzlib-3.3.2.jar:d34e523278e5dff565eba3ef3c089d515b2b5cc7b47dc77e2f3465e5e47176ac', 'com.android.tools.build:apkzlib:3.4.0:apkzlib-3.4.0.jar:8653c85f5fdf1dde840e8b8af7396aeb79c34b66e541b5860059616006535592',
'com.android.tools.build:builder-model:3.3.2:builder-model-3.3.2.jar:055e3db0ecee9e06b9f024034999a29cd92cb1885207b37542126bd8bcc57f46', 'com.android.tools.build:builder-model:3.4.0:builder-model-3.4.0.jar:a88f138124a9f016a70bcb4760359a502f65c7deed56507ee4014f4dd9ea853b',
'com.android.tools.build:builder-test-api:3.3.2:builder-test-api-3.3.2.jar:0b2e4cd7615bbcad14a3c91fe45ae26693508d06e40ba06c5968b8bc24416618', 'com.android.tools.build:builder-test-api:3.4.0:builder-test-api-3.4.0.jar:31089ab1ec19ca7687a010867d2f3807513c805b8226979706f4247b5d4df26f',
'com.android.tools.build:builder:3.3.2:builder-3.3.2.jar:65649704da7aef0487235fd326f0f2e99ed5cf958e80f204496e6e08a42bd9f5', 'com.android.tools.build:builder:3.4.0:builder-3.4.0.jar:476221b5203a7f50089bf185ed95000a34b6f5020ef0a17815afd58606922679',
'com.android.tools.build:gradle-api:3.3.2:gradle-api-3.3.2.jar:3cbd47e41bb70330dd72ec2c9fe51e6173554b484a03829b5a2de9e00841e040', 'com.android.tools.build:gradle-api:3.4.0:gradle-api-3.4.0.jar:215eca38f6719213c2f492b4d622cdd11676c66c9871f8a2aed0c66d00175628',
'com.android.tools.build:manifest-merger:26.3.2:manifest-merger-26.3.2.jar:05c4a6d8b02fb9f08744876477d0a68547c03a8a9069b1f086684fa04af97c33', 'com.android.tools.build:manifest-merger:26.4.0:manifest-merger-26.4.0.jar:29e45e690dedd165035e97c21c2ca94d0bd4ec16b6b210daa26669a582b6f220',
'com.android.tools.ddms:ddmlib:26.3.2:ddmlib-26.3.2.jar:d248da8a563d6e46d2c7ebbf371a4877e00510f4ca763c0bb272d5a281bf8b85', 'com.android.tools.ddms:ddmlib:26.4.0:ddmlib-26.4.0.jar:93f56fe4630c3166adbd6c51d7bb602d96abb91b07ba5b1165fdcd071e88c940',
'com.android.tools.external.com-intellij:intellij-core:26.3.2:intellij-core-26.3.2.jar:6c5ecc968230e9f4dcd0fef28885379feace1f0cd8130de6f61d649c86139bf3', 'com.android.tools.external.com-intellij:intellij-core:26.4.0:intellij-core-26.4.0.jar:30cb0e879d4424de9677a50b537fb628636b4a50f5470af5e52437980c41421f',
'com.android.tools.external.com-intellij:kotlin-compiler:26.3.2:kotlin-compiler-26.3.2.jar:1007d9b07ccb49cd8eaf30fda10ed4681d4714f2f9ab2ecda39b4e539cc51bbe', 'com.android.tools.external.com-intellij:kotlin-compiler:26.4.0:kotlin-compiler-26.4.0.jar:dd1fe225c31a0e012dc025336363a5b783e2c5c20ffb69e77f8f57e89420d998',
'com.android.tools.external.org-jetbrains:uast:26.3.2:uast-26.3.2.jar:5d1833e562ea4f38a89708dfde695f0a162cbd39d003d3dde818c3fdc2b05317', 'com.android.tools.external.org-jetbrains:uast:26.4.0:uast-26.4.0.jar:f25f3285b775a983327583ff6584dea54e447813ef69e0ce08b05a45b5f4aab0',
'com.android.tools.layoutlib:layoutlib-api:26.3.2:layoutlib-api-26.3.2.jar:d7e61e874ab95f5c350dd38b6a95b5c9dbe0083a02001884264cdb390cb255b8', 'com.android.tools.layoutlib:layoutlib-api:26.4.0:layoutlib-api-26.4.0.jar:52128f5cf293b224072be361919bfd416e59480ab7264ddcdbbf046b0d7a12e3',
'com.android.tools.lint:lint-api:26.3.2:lint-api-26.3.2.jar:5867dfd7fb4a4e161a816a5d29d045f9b542d34594c00a1efec46fb4cd0e1033', 'com.android.tools.lint:lint-api:26.4.0:lint-api-26.4.0.jar:fdb8fca8ae4c254f438338d03d72605e00ed106f2d5550405af41ca1c8509401',
'com.android.tools.lint:lint-checks:26.3.2:lint-checks-26.3.2.jar:4b163b9c93790d2771e92ba8de58a0d9e0671ffcf2ccef3cf496efd442e27517', 'com.android.tools.lint:lint-checks:26.4.0:lint-checks-26.4.0.jar:4ff52d40488cd3e22b9c6b2eb67784e0c3269d0b42ef9d17689cd75a7b2bceb4',
'com.android.tools.lint:lint-gradle-api:26.3.2:lint-gradle-api-26.3.2.jar:54cb282e0c054f9bed3f51302ce08b003c8ab7961dfd5a4f6de26c23cc23062f', 'com.android.tools.lint:lint-gradle-api:26.4.0:lint-gradle-api-26.4.0.jar:714b7a85c7d2aa10daeab16e969fe7530c659d0728a7f24021da456870418d0f',
'com.android.tools.lint:lint-gradle:26.3.2:lint-gradle-26.3.2.jar:bb139615f4ce97d42cc394b9389b49b76a6eb85be6785a5d272991543b519013', 'com.android.tools.lint:lint-gradle:26.4.0:lint-gradle-26.4.0.jar:b8c130d273f522388734457e1b96790f41528fcec6fda9e8eaa4e4d95a07cfbb',
'com.android.tools.lint:lint:26.3.2:lint-26.3.2.jar:ef7b369f8a56a92ccb0f4c1c357666b9339e4a711a9d84747d446441746cfe4e', 'com.android.tools.lint:lint:26.4.0:lint-26.4.0.jar:83aa062fb0405b60ed358d858c8c2955e1bae44a455b498068c6a60988755f00',
'com.android.tools:annotations:26.3.2:annotations-26.3.2.jar:5bcce8e98b6a2f5ccf13ebcefd8f734e0b35f8b19e456575665631442ce1f7a1', 'com.android.tools:annotations:26.4.0:annotations-26.4.0.jar:a7955b8e19c3a2a861d6faa43a58b7c0d46ea9112188ee3e235c6f9f439ecc1a',
'com.android.tools:common:26.3.2:common-26.3.2.jar:d9f8e7f0669e9a701568e3db6a87c89cf12d8fa6811c9991e969f950215ecfac', 'com.android.tools:common:26.4.0:common-26.4.0.jar:ea40b94b3c1284ea7700f011388e2906a8363a66abd902891722b3c557984852',
'com.android.tools:dvlib:26.3.2:dvlib-26.3.2.jar:d84aad56161c7773579303d69714ded6897c64c6ddfd7d456e453231e4dfe811', 'com.android.tools:dvlib:26.4.0:dvlib-26.4.0.jar:23af89c535b01ba36ceed1b6b309b672814eba624e643cd7dedf0519edad50cc',
'com.android.tools:repository:26.3.2:repository-26.3.2.jar:da611eeb06e9ab8750d25b9e2901e10db8e5ec6304eb4c8b7103d39e0921ea40', 'com.android.tools:repository:26.4.0:repository-26.4.0.jar:3d1763ab46199374dc6d94129bba11c70f1d5857e2c81a3ac4898abca40b176b',
'com.android.tools:sdk-common:26.3.2:sdk-common-26.3.2.jar:82823a3bf25e64fac33a286490f0cf5ac50c2cdb3c540149b030896bb44bf96c', 'com.android.tools:sdk-common:26.4.0:sdk-common-26.4.0.jar:78a522525b30ffc6b7bf1299c831d24ce385f68a9f4878f8f752e9baefa31b0f',
'com.android.tools:sdklib:26.3.2:sdklib-26.3.2.jar:424d15492af67321900963238646d27495ab60de2a5b19e6a416963bc5d6932b', 'com.android.tools:sdklib:26.4.0:sdklib-26.4.0.jar:b854c23892013a326d761cf071c72cf3e038ed0469d10f4a356829fa56e4c132',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed', 'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'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.0:gson-2.8.0.jar:c6221763bd79c4f1c3dc7f750b5f29a0bb38b367b81314c4f71896e340c40825',
'com.google.dagger:dagger-compiler:2.19:dagger-compiler-2.19.jar:27a4b202a2de908182edb261f8c0a264e08e5e4733d7514bc7fbf0d31da5c0fc', 'com.google.dagger:dagger-compiler:2.22.1:dagger-compiler-2.22.1.jar:e5f28302cbe70a79d3620cddebfb8ec0736814f3980ffe1e673bfe3342f507d3',
'com.google.dagger:dagger-producers:2.19:dagger-producers-2.19.jar:a17663abe0fc38b676026950907d4c5f5e2bf338375415861eaff6e3bdb0b768', 'com.google.dagger:dagger-producers:2.22.1:dagger-producers-2.22.1.jar:f834a0082014213a68ff06a0f048d750178d02196c58b0b15beb367d32b97e35',
'com.google.dagger:dagger-spi:2.19:dagger-spi-2.19.jar:e7a6379d82c841f6aac2866948ad1eed716528707814602842a8d844ce04e2e1', 'com.google.dagger:dagger-spi:2.22.1:dagger-spi-2.22.1.jar:4b0b922793b3bcb91b99fabb75dba77c68afd7ae4c5f0c4fd6ba681f0a291c7d',
'com.google.dagger:dagger:2.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939', 'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8', 'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8',
'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:guava:25.0-jre:guava-25.0-jre.jar:3fd4341776428c7e0e5c18a7c10de129475b69ab9d30aeafbb5c277bb6074fa9',
'com.google.guava:guava:26.0-jre:guava-26.0-jre.jar:a0e9cabad665bc20bcd2b01f108e5fc03f756e13aea80abaadb9f407033bea2c', 'com.google.guava:guava:26.0-jre:guava-26.0-jre.jar:a0e9cabad665bc20bcd2b01f108e5fc03f756e13aea80abaadb9f407033bea2c',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f', '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',
'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439', 'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
@@ -48,7 +48,7 @@ dependencyVerification {
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce', 'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4', 'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4',
'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038', 'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038',
'commons-codec:commons-codec:1.9:commons-codec-1.9.jar:ad19d2601c3abf0b946b5c3a4113e226a8c1e3305e395b90013b78dd94a723ce', 'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636', 'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c', 'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
@@ -60,9 +60,9 @@ dependencyVerification {
'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',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6', 'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.httpcomponents:httpclient:4.5.2:httpclient-4.5.2.jar:0dffc621400d6c632f55787d996b8aeca36b30746a716e079a985f24d8074057', 'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.5:httpcore-4.4.5.jar:64d5453874cab7e40a7065cb01a9a9ca1053845a9786b478878b679e0580cec3', 'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents:httpmime:4.5.2:httpmime-4.5.2.jar:231a3f7e4962053db2be8461d5422e68fc458a3a7dd7d8ada803a348e21f8f07', 'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.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',

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.19" implementation "com.google.dagger:dagger:2.22.1"
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,18 @@
package org.briarproject.bramble.api;
/**
* Thrown when data being parsed uses a protocol or format version that is not
* supported.
*/
public class UnsupportedVersionException extends FormatException {
private final boolean tooOld;
public UnsupportedVersionException(boolean tooOld) {
this.tooOld = tooOld;
}
public boolean isTooOld() {
return tooOld;
}
}

View File

@@ -33,7 +33,8 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* *
* @return whether or not this message should be shared * @param txn A read-write transaction
* @return Whether or not this message should be shared
* @throws DbException Should only be used for real database errors. * @throws DbException Should only be used for real database errors.
* If this is thrown, delivery will be attempted again at next startup, * If this is thrown, delivery will be attempted again at next startup,
* whereas if a FormatException is thrown, the message will be permanently * whereas if a FormatException is thrown, the message will be permanently

View File

@@ -18,10 +18,11 @@ public interface ContactGroupFactory {
* Creates a group for the given client to share with the given contact. * Creates a group for the given client to share with the given contact.
*/ */
Group createContactGroup(ClientId clientId, int majorVersion, Group createContactGroup(ClientId clientId, int majorVersion,
Contact contact, AuthorId local); Contact contact);
/** /**
* Creates a group for the given client to share between the given authors. * Creates a group for the given client to share between the given authors
* identified by their AuthorIds.
*/ */
Group createContactGroup(ClientId clientId, int majorVersion, Group createContactGroup(ClientId clientId, int majorVersion,
AuthorId authorId1, AuthorId authorId2); AuthorId authorId1, AuthorId authorId2);

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api.contact; package org.briarproject.bramble.api.contact;
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.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -16,14 +17,16 @@ public class Contact {
private final ContactId id; private final ContactId id;
private final Author author; private final Author author;
private final AuthorId localAuthorId;
@Nullable @Nullable
private final String alias; private final String alias;
@Nullable @Nullable
private final byte[] handshakePublicKey; private final byte[] handshakePublicKey;
private final boolean verified; private final boolean verified;
public Contact(ContactId id, Author author, @Nullable String alias, public Contact(ContactId id, Author author, AuthorId localAuthorId,
@Nullable byte[] handshakePublicKey, boolean verified) { @Nullable String alias, @Nullable byte[] handshakePublicKey,
boolean verified) {
if (alias != null) { if (alias != null) {
int aliasLength = toUtf8(alias).length; int aliasLength = toUtf8(alias).length;
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH) if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
@@ -35,6 +38,7 @@ public class Contact {
} }
this.id = id; this.id = id;
this.author = author; this.author = author;
this.localAuthorId = localAuthorId;
this.alias = alias; this.alias = alias;
this.handshakePublicKey = handshakePublicKey; this.handshakePublicKey = handshakePublicKey;
this.verified = verified; this.verified = verified;
@@ -48,6 +52,10 @@ public class Contact {
return author; return author;
} }
public AuthorId getLocalAuthorId() {
return localAuthorId;
}
@Nullable @Nullable
public String getAlias() { public String getAlias() {
return alias; return alias;

View File

@@ -1,7 +1,10 @@
package org.briarproject.bramble.api.contact; package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.FormatException;
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.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
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;
@@ -24,60 +27,63 @@ public interface ContactManager {
void registerContactHook(ContactHook hook); void registerContactHook(ContactHook hook);
/** /**
* Stores a contact with the given pseudonym, derives and stores transport * Stores a contact associated with the given local and remote pseudonyms,
* keys for each transport, and returns an ID for the contact. * derives and stores transport keys for each transport, and returns an ID
* for the contact.
* *
* @param alice true if the local party is Alice * @param alice true if the local party is Alice
*/ */
ContactId addContact(Transaction txn, Author a, SecretKey rootKey, ContactId addContact(Transaction txn, Author remote, AuthorId local,
SecretKey rootKey, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException;
/**
* Stores a contact associated with the given local and remote pseudonyms
* and returns an ID for the contact.
*/
ContactId addContact(Transaction txn, Author remote, AuthorId local,
boolean verified) throws DbException;
/**
* Stores a contact associated with the given local and remote pseudonyms,
* derives and stores transport keys for each transport, and returns an ID
* for the contact.
*
* @param alice true if the local party is Alice
*/
ContactId addContact(Author remote, AuthorId local, SecretKey rootKey,
long timestamp, boolean alice, boolean verified, boolean active) long timestamp, boolean alice, boolean verified, boolean active)
throws DbException; throws DbException;
/** /**
* Stores a contact with the given pseudonym and returns an ID for the * Returns the handshake link that needs to be sent to a contact we want
* contact. * to add.
*/ */
ContactId addContact(Transaction txn, Author a, boolean verified) String getHandshakeLink() throws DbException;
throws DbException;
/** /**
* Stores a contact with the given pseudonym, derives and stores transport * Creates a {@link PendingContact} from the given handshake link and
* keys for each transport, and returns an ID for the contact. * alias, adds it to the database and returns it.
* *
* @param alice true if the local party is Alice * @param link The handshake link received from the contact we want to add
* @param alias The alias the user has given this contact
* @return A PendingContact representing the contact to be added
* @throws UnsupportedVersionException If the link uses a format version
* that is not supported
* @throws FormatException If the link is invalid
*/ */
ContactId addContact(Author a, SecretKey rootKey, long timestamp, PendingContact addPendingContact(String link, String alias)
boolean alice, boolean verified, boolean active) throws DbException; throws DbException, FormatException;
/**
* Returns the static link that needs to be sent to the contact to be added.
*/
String getRemoteContactLink();
/**
* Returns true if the given link is syntactically valid.
*/
boolean isValidRemoteContactLink(String link);
/**
* Requests a new contact to be added via the given {@code link}.
*
* @param link The link received from the contact we want to add.
* @param alias The alias the user has given this contact.
* @return A PendingContact representing the contact to be added.
*/
PendingContact addRemoteContactRequest(String link, String alias);
/** /**
* Returns a list of {@link PendingContact}s. * Returns a list of {@link PendingContact}s.
*/ */
Collection<PendingContact> getPendingContacts(); Collection<PendingContact> getPendingContacts() throws DbException;
/** /**
* Removes a {@link PendingContact} that is in state * Removes a {@link PendingContact}.
* {@link PendingContactState FAILED}.
*/ */
void removePendingContact(PendingContact pendingContact); void removePendingContact(PendingContactId p) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given ID.
@@ -85,14 +91,22 @@ public interface ContactManager {
Contact getContact(ContactId c) throws DbException; Contact getContact(ContactId c) throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId
*
* @throws NoSuchContactException If the contact is not in the database
*/ */
Contact getContact(AuthorId a) throws DbException; Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/** /**
* Returns the contact with the given ID. * Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId
*
* @throws NoSuchContactException If the contact is not in the database
*/ */
Contact getContact(Transaction txn, AuthorId a) throws DbException; Contact getContact(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException;
/** /**
* Returns all active contacts. * Returns all active contacts.
@@ -122,14 +136,16 @@ public interface ContactManager {
throws DbException; throws DbException;
/** /**
* Returns true if a contact with this pseudonym already exists. * Return true if a contact with this name and public key already exists
*/ */
boolean contactExists(Transaction txn, AuthorId a) throws DbException; boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException;
/** /**
* Returns true if a contact with this pseudonym already exists. * Return true if a contact with this name and public key already exists
*/ */
boolean contactExists(AuthorId a) throws DbException; boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/** /**
* Returns the {@link AuthorInfo} for the given author. * Returns the {@link AuthorInfo} for the given author.
@@ -143,8 +159,20 @@ public interface ContactManager {
interface ContactHook { interface ContactHook {
/**
* Called when a contact is being added.
*
* @param txn A read-write transaction
* @param c The contact that is being added
*/
void addingContact(Transaction txn, Contact c) throws DbException; void addingContact(Transaction txn, Contact c) throws DbException;
/**
* Called when a contact is being removed
*
* @param txn A read-write transaction
* @param c The contact that is being removed
*/
void removingContact(Transaction txn, Contact c) throws DbException; void removingContact(Transaction txn, Contact c) throws DbException;
} }
} }

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.api.contact;
import java.util.regex.Pattern;
public interface HandshakeLinkConstants {
/**
* The current version of the handshake link format.
*/
int FORMAT_VERSION = 0;
/**
* The length of a base32-encoded handshake link in bytes, excluding the
* 'briar://' prefix.
*/
int BASE32_LINK_BYTES = 53;
/**
* The length of a raw handshake link in bytes, before base32 encoding.
*/
int RAW_LINK_BYTES = 33;
/**
* Regular expression for matching handshake links, including or excluding
* the 'briar://' prefix.
*/
Pattern LINK_REGEX =
Pattern.compile("(briar://)?([a-z2-7]{" + BASE32_LINK_BYTES + "})");
/**
* Label for hashing handshake public keys to calculate their identifiers.
*/
String ID_LABEL = "org.briarproject.bramble/HANDSHAKE_KEY_ID";
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.api.introduction.event; package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
@@ -8,11 +8,11 @@ import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class IntroductionSucceededEvent extends Event { public class ContactAddedRemotelyEvent extends Event {
private final Contact contact; private final Contact contact;
public IntroductionSucceededEvent(Contact contact) { public ContactAddedRemotelyEvent(Contact contact) {
this.contact = contact; this.contact = contact;
} }

View File

@@ -0,0 +1,26 @@
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a pending contact is removed.
*/
@Immutable
@NotNullByDefault
public class PendingContactRemovedEvent extends Event {
private final PendingContactId id;
public PendingContactRemovedEvent(PendingContactId id) {
this.id = id;
}
public PendingContactId getId() {
return id;
}
}

View File

@@ -7,7 +7,7 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
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;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.Identity;
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.plugin.TransportId;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -102,11 +102,11 @@ public interface DatabaseComponent {
NullableDbCallable<R, E> task) throws DbException, E; NullableDbCallable<R, E> task) throws DbException, E;
/** /**
* Stores a contact with the given pseudonym and returns an ID for the * Stores a contact associated with the given local and remote pseudonyms,
* contact. * and returns an ID for the contact.
*/ */
ContactId addContact(Transaction txn, Author a, boolean verified) ContactId addContact(Transaction txn, Author remote, AuthorId local,
throws DbException; boolean verified) throws DbException;
/** /**
* Stores a group. * Stores a group.
@@ -128,9 +128,9 @@ public interface DatabaseComponent {
HandshakeKeys k) throws DbException; HandshakeKeys k) throws DbException;
/** /**
* Stores a local pseudonym. * Stores an identity.
*/ */
void addLocalAuthor(Transaction txn, LocalAuthor a) throws DbException; void addIdentity(Transaction txn, Identity i) throws DbException;
/** /**
* Stores a local message. * Stores a local message.
@@ -158,11 +158,13 @@ public interface DatabaseComponent {
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/** /**
* Returns true if the database contains the given contact. * Returns true if the database contains the given contact for the given
* local pseudonym.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
boolean containsContact(Transaction txn, AuthorId a) throws DbException; boolean containsContact(Transaction txn, AuthorId remote, AuthorId local)
throws DbException;
/** /**
* Returns true if the database contains the given group. * Returns true if the database contains the given group.
@@ -172,12 +174,12 @@ public interface DatabaseComponent {
boolean containsGroup(Transaction txn, GroupId g) throws DbException; boolean containsGroup(Transaction txn, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given local author. * Returns true if the database contains an identity for the given
* pseudonym.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
boolean containsLocalAuthor(Transaction txn, AuthorId local) boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
throws DbException;
/** /**
* Returns true if the database contains the given pending contact. * Returns true if the database contains the given pending contact.
@@ -252,13 +254,6 @@ public interface DatabaseComponent {
*/ */
Contact getContact(Transaction txn, ContactId c) throws DbException; Contact getContact(Transaction txn, ContactId c) throws DbException;
/**
* Returns the contact with the given author ID.
* <p/>
* Read-only.
*/
Contact getContact(Transaction txn, AuthorId a) throws DbException;
/** /**
* Returns all contacts. * Returns all contacts.
* <p/> * <p/>
@@ -266,6 +261,22 @@ public interface DatabaseComponent {
*/ */
Collection<Contact> getContacts(Transaction txn) throws DbException; Collection<Contact> getContacts(Transaction txn) throws DbException;
/**
* Returns a possibly empty collection of contacts with the given author ID.
* <p/>
* Read-only.
*/
Collection<Contact> getContactsByAuthorId(Transaction txn, AuthorId remote)
throws DbException;
/**
* Returns all contacts associated with the given local pseudonym.
* <p/>
* Read-only.
*/
Collection<ContactId> getContacts(Transaction txn, AuthorId a)
throws DbException;
/** /**
* Returns the group with the given ID. * Returns the group with the given ID.
* <p/> * <p/>
@@ -306,18 +317,18 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Returns the local pseudonym with the given ID. * Returns the identity for the local pseudonym with the given ID.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
LocalAuthor getLocalAuthor(Transaction txn, AuthorId a) throws DbException; Identity getIdentity(Transaction txn, AuthorId a) throws DbException;
/** /**
* Returns all local pseudonyms. * Returns the identities for all local pseudonyms.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException; Collection<Identity> getIdentities(Transaction txn) throws DbException;
/** /**
* Returns the message with the given ID. * Returns the message with the given ID.
@@ -548,9 +559,9 @@ public interface DatabaseComponent {
HandshakeKeySetId k) throws DbException; HandshakeKeySetId k) throws DbException;
/** /**
* Removes a local pseudonym (and all associated state) from the database. * Removes an identity (and all associated state) from the database.
*/ */
void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException; void removeIdentity(Transaction txn, AuthorId a) throws DbException;
/** /**
* Removes a message (and all associated state) from the database. * Removes a message (and all associated state) from the database.
@@ -608,6 +619,12 @@ public interface DatabaseComponent {
void addMessageDependencies(Transaction txn, Message dependent, void addMessageDependencies(Transaction txn, Message dependent,
Collection<MessageId> dependencies) throws DbException; Collection<MessageId> dependencies) throws DbException;
/**
* Sets the handshake key pair for the identity with the given ID.
*/
void setHandshakeKeyPair(Transaction txn, AuthorId local, byte[] publicKey,
byte[] privateKey) throws DbException;
/** /**
* Sets the reordering window for the given transport key set in the given * Sets the reordering window for the given transport key set in the given
* time period. * time period.

View File

@@ -1,9 +1,9 @@
package org.briarproject.bramble.api.db; package org.briarproject.bramble.api.db;
/** /**
* Thrown when a database operation is attempted for a pseudonym that is not in * Thrown when a database operation is attempted for an identity that is not in
* the database. This exception may occur due to concurrent updates and does * the database. This exception may occur due to concurrent updates and does
* not indicate a database error. * not indicate a database error.
*/ */
public class NoSuchLocalAuthorException extends DbException { public class NoSuchIdentityException extends DbException {
} }

View File

@@ -18,14 +18,7 @@ public interface AuthorFactory {
/** /**
* Creates a local author with the current format version and the given * Creates a local author with the current format version and the given
* name and keys. * name.
*/ */
LocalAuthor createLocalAuthor(String name, byte[] publicKey, LocalAuthor createLocalAuthor(String name);
byte[] privateKey);
/**
* Creates a local author with the given format version, name and keys.
*/
LocalAuthor createLocalAuthor(int formatVersion, String name,
byte[] publicKey, byte[] privateKey);
} }

View File

@@ -0,0 +1,96 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Arrays;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
@Immutable
@NotNullByDefault
public class Identity {
private final LocalAuthor localAuthor;
@Nullable
private final byte[] handshakePublicKey, handshakePrivateKey;
private final long created;
public Identity(LocalAuthor localAuthor,
@Nullable byte[] handshakePublicKey,
@Nullable byte[] handshakePrivateKey, long created) {
if (handshakePublicKey != null) {
int keyLength = handshakePublicKey.length;
if (keyLength == 0 || keyLength > MAX_AGREEMENT_PUBLIC_KEY_BYTES)
throw new IllegalArgumentException();
}
this.localAuthor = localAuthor;
this.handshakePublicKey = handshakePublicKey;
this.handshakePrivateKey = handshakePrivateKey;
this.created = created;
}
/**
* Returns the ID of the user's pseudonym.
*/
public AuthorId getId() {
return localAuthor.getId();
}
/**
* Returns the user's pseudonym.
*/
public LocalAuthor getLocalAuthor() {
return localAuthor;
}
/**
* Returns true if the identity has a handshake key pair.
*/
public boolean hasHandshakeKeyPair() {
return handshakePublicKey != null && handshakePrivateKey != null;
}
/**
* Returns the public key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePublicKey() {
return handshakePublicKey;
}
/**
* Returns the private key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePrivateKey() {
return handshakePrivateKey;
}
/**
* Returns the time the identity was created, in milliseconds since the
* Unix epoch.
*/
public long getTimeCreated() {
return created;
}
@Override
public int hashCode() {
return localAuthor.getId().hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof Identity) {
Identity i = (Identity) o;
return created == i.created &&
localAuthor.equals(i.localAuthor) &&
Arrays.equals(handshakePublicKey, i.handshakePublicKey) &&
Arrays.equals(handshakePrivateKey, i.handshakePrivateKey);
}
return false;
}
}

View File

@@ -1,30 +1,29 @@
package org.briarproject.bramble.api.identity; package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
public interface IdentityManager { public interface IdentityManager {
/** /**
* Creates a local identity with the given name. * Creates an identity with the given name. The identity includes a
* handshake key pair.
*/ */
@CryptoExecutor @CryptoExecutor
LocalAuthor createLocalAuthor(String name); Identity createIdentity(String name);
/** /**
* Registers the given local identity with the manager. The identity is * Registers the given identity with the manager. This method should be
* not stored until {@link #storeLocalAuthor()} is called. * called before {@link LifecycleManager#startServices(SecretKey)}. The
* identity is stored when {@link LifecycleManager#startServices(SecretKey)}
* is called. The identity must include a handshake key pair.
*/ */
void registerLocalAuthor(LocalAuthor a); void registerIdentity(Identity i);
/**
* Stores the local identity registered with
* {@link #registerLocalAuthor(LocalAuthor)}, if any.
*/
void storeLocalAuthor() throws DbException;
/** /**
* Returns the cached local identity or loads it from the database. * Returns the cached local identity or loads it from the database.
@@ -33,7 +32,18 @@ public interface IdentityManager {
/** /**
* Returns the cached local identity or loads it from the database. * Returns the cached local identity or loads it from the database.
* <p/>
* Read-only.
*/ */
LocalAuthor getLocalAuthor(Transaction txn) throws DbException; LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
/**
* Returns the cached handshake keys or loads them from the database.
* <p/>
* Read-only.
*
* @return A two-element array containing the public key in the first
* element and the private key in the second
*/
byte[][] getHandshakeKeys(Transaction txn) throws DbException;
} }

View File

@@ -2,11 +2,8 @@ package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
/** /**
* A pseudonym for the local user. * A pseudonym for the local user.
*/ */
@@ -15,31 +12,11 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
public class LocalAuthor extends Author { public class LocalAuthor extends Author {
private final byte[] privateKey; private final byte[] privateKey;
@Nullable
private final byte[] handshakePublicKey, handshakePrivateKey;
private final long created;
public LocalAuthor(AuthorId id, int formatVersion, String name, public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, long created) { byte[] publicKey, byte[] privateKey) {
super(id, formatVersion, name, publicKey); super(id, formatVersion, name, publicKey);
this.privateKey = privateKey; this.privateKey = privateKey;
this.created = created;
handshakePublicKey = null;
handshakePrivateKey = null;
}
public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, byte[] handshakePublicKey,
byte[] handshakePrivateKey, long created) {
super(id, formatVersion, name, publicKey);
if (handshakePublicKey.length == 0 ||
handshakePublicKey.length > MAX_PUBLIC_KEY_LENGTH) {
throw new IllegalArgumentException();
}
this.privateKey = privateKey;
this.handshakePublicKey = handshakePublicKey;
this.handshakePrivateKey = handshakePrivateKey;
this.created = created;
} }
/** /**
@@ -48,28 +25,4 @@ public class LocalAuthor extends Author {
public byte[] getPrivateKey() { public byte[] getPrivateKey() {
return privateKey; return privateKey;
} }
/**
* Returns the public key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePublicKey() {
return handshakePublicKey;
}
/**
* Returns the private key used for handshaking, or null if no key exists.
*/
@Nullable
public byte[] getHandshakePrivateKey() {
return handshakePrivateKey;
}
/**
* Returns the time the pseudonym was created, in milliseconds since the
* Unix epoch.
*/
public long getTimeCreated() {
return created;
}
} }

View File

@@ -7,15 +7,15 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a local pseudonym is added. * An event that is broadcast when an identity is added.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class LocalAuthorAddedEvent extends Event { public class IdentityAddedEvent extends Event {
private final AuthorId authorId; private final AuthorId authorId;
public LocalAuthorAddedEvent(AuthorId authorId) { public IdentityAddedEvent(AuthorId authorId) {
this.authorId = authorId; this.authorId = authorId;
} }

View File

@@ -7,15 +7,15 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event that is broadcast when a local pseudonym is removed. * An event that is broadcast when an identity is removed.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class LocalAuthorRemovedEvent extends Event { public class IdentityRemovedEvent extends Event {
private final AuthorId authorId; private final AuthorId authorId;
public LocalAuthorRemovedEvent(AuthorId authorId) { public IdentityRemovedEvent(AuthorId authorId) {
this.authorId = authorId; this.authorId = authorId;
} }

View File

@@ -1,20 +0,0 @@
package org.briarproject.bramble.api.keyagreement;
import java.io.IOException;
/**
* Thrown when a QR code that has been scanned uses a protocol version that is
* not supported.
*/
public class UnsupportedVersionException extends IOException {
private final boolean tooOld;
public UnsupportedVersionException(boolean tooOld) {
this.tooOld = tooOld;
}
public boolean isTooOld() {
return tooOld;
}
}

View File

@@ -2,16 +2,16 @@ package org.briarproject.bramble.api.lifecycle;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
/** /**
* Manages the lifecycle of the app, starting {@link Client Clients}, starting * Manages the lifecycle of the app: opening and closing the
* and stopping {@link Service Services}, shutting down * {@link DatabaseComponent} starting and stopping {@link Service Services},
* {@link ExecutorService ExecutorServices}, and opening and closing the * and shutting down {@link ExecutorService ExecutorServices}.
* {@link DatabaseComponent}.
*/ */
@NotNullByDefault @NotNullByDefault
public interface LifecycleManager { public interface LifecycleManager {
@@ -42,18 +42,19 @@ public interface LifecycleManager {
} }
} }
/**
* Registers a hook to be called after the database is opened and before
* {@link Service services} are started. This method should be called
* before {@link #startServices(SecretKey)}.
*/
void registerOpenDatabaseHook(OpenDatabaseHook hook);
/** /**
* Registers a {@link Service} to be started and stopped. This method * Registers a {@link Service} to be started and stopped. This method
* should be called before {@link #startServices(SecretKey)}. * should be called before {@link #startServices(SecretKey)}.
*/ */
void registerService(Service s); void registerService(Service s);
/**
* Registers a {@link Client} to be started. This method should be called
* before {@link #startServices(SecretKey)}.
*/
void registerClient(Client c);
/** /**
* Registers an {@link ExecutorService} to be shut down. This method * Registers an {@link ExecutorService} to be shut down. This method
* should be called before {@link #startServices(SecretKey)}. * should be called before {@link #startServices(SecretKey)}.
@@ -62,7 +63,7 @@ public interface LifecycleManager {
/** /**
* Opens the {@link DatabaseComponent} using the given key and starts any * Opens the {@link DatabaseComponent} using the given key and starts any
* registered {@link Client Clients} and {@link Service Services}. * registered {@link Service Services}.
*/ */
StartResult startServices(SecretKey dbKey); StartResult startServices(SecretKey dbKey);
@@ -80,8 +81,7 @@ public interface LifecycleManager {
/** /**
* Waits for the {@link DatabaseComponent} to be opened and all registered * Waits for the {@link DatabaseComponent} to be opened and all registered
* {@link Client Clients} and {@link Service Services} to start before * {@link Service Services} to start before returning.
* returning.
*/ */
void waitForStartup() throws InterruptedException; void waitForStartup() throws InterruptedException;
@@ -97,4 +97,13 @@ public interface LifecycleManager {
*/ */
LifecycleState getLifecycleState(); LifecycleState getLifecycleState();
interface OpenDatabaseHook {
/**
* Called when the database is being opened, before
* {@link #waitForDatabase()} returns.
*
* @param txn A read-write transaction
*/
void onDatabaseOpened(Transaction txn) throws DbException;
}
} }

View File

@@ -1,14 +0,0 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface Client {
/**
* Called at startup to create any local state needed by the client.
*/
void createLocalState(Transaction txn) throws DbException;
}

View File

@@ -11,7 +11,8 @@ public interface IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* *
* @return whether or not this message should be shared * @param txn A read-write transaction
* @return Whether or not this message should be shared
* @throws DbException Should only be used for real database errors. * @throws DbException Should only be used for real database errors.
* If this is thrown, delivery will be attempted again at next startup, * If this is thrown, delivery will be attempted again at next startup,
* whereas if an InvalidMessageException is thrown, * whereas if an InvalidMessageException is thrown,

View File

@@ -46,7 +46,14 @@ public interface ClientVersioningManager {
ClientId clientId, int majorVersion) throws DbException; ClientId clientId, int majorVersion) throws DbException;
interface ClientVersioningHook { interface ClientVersioningHook {
/**
* Called when the visibility of a client with respect to a contact is
* changing.
*
* @param txn A read-write transaction
* @param c The contact affected by the visibility change
* @param v The new visibility of the client
*/
void onClientVisibilityChanging(Transaction txn, Contact c, void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException; Visibility v) throws DbException;
} }

View File

@@ -0,0 +1,75 @@
package org.briarproject.bramble.util;
import java.io.ByteArrayOutputStream;
public class Base32 {
private static final char[] DIGITS = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '2', '3', '4', '5', '6', '7'
};
public static String encode(byte[] b) {
StringBuilder s = new StringBuilder();
int byteIndex = 0, currentCode = 0x00;
int byteMask = 0x80, codeMask = 0x10;
while (byteIndex < b.length) {
if ((b[byteIndex] & byteMask) != 0) currentCode |= codeMask;
// After every 8 bits, move on to the next byte
if (byteMask == 0x01) {
byteMask = 0x80;
byteIndex++;
} else {
byteMask >>>= 1;
}
// After every 5 bits, move on to the next digit
if (codeMask == 0x01) {
s.append(DIGITS[currentCode]);
codeMask = 0x10;
currentCode = 0x00;
} else {
codeMask >>>= 1;
}
}
// If we're part-way through a digit, output it
if (codeMask != 0x10) s.append(DIGITS[currentCode]);
return s.toString();
}
public static byte[] decode(String s, boolean strict) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
int digitIndex = 0, digitCount = s.length(), currentByte = 0x00;
int byteMask = 0x80, codeMask = 0x10;
while (digitIndex < digitCount) {
int code = decodeDigit(s.charAt(digitIndex));
if ((code & codeMask) != 0) currentByte |= byteMask;
// After every 8 bits, move on to the next byte
if (byteMask == 0x01) {
b.write(currentByte);
byteMask = 0x80;
currentByte = 0x00;
} else {
byteMask >>>= 1;
}
// After every 5 bits, move on to the next digit
if (codeMask == 0x01) {
codeMask = 0x10;
digitIndex++;
} else {
codeMask >>>= 1;
}
}
// If any extra bits were used for encoding, they should all be zero
if (strict && byteMask != 0x80 && currentByte != 0x00)
throw new IllegalArgumentException();
return b.toByteArray();
}
private static int decodeDigit(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a';
if (c >= '2' && c <= '7') return c - '2' + 26;
throw new IllegalArgumentException("Not a base32 digit: " + c);
}
}

View File

@@ -153,4 +153,13 @@ public class StringUtils {
return new String(c); return new String(c);
} }
public static String getRandomBase32String(int length) {
char[] c = new char[length];
for (int i = 0; i < length; i++) {
int character = random.nextInt(32);
if (character < 26) c[i] = (char) ('a' + character);
else c[i] = (char) ('2' + (character - 26));
}
return new String(c);
}
} }

View File

@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
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;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -30,6 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -99,25 +101,26 @@ public class TestUtils {
return new SecretKey(getRandomBytes(SecretKey.LENGTH)); return new SecretKey(getRandomBytes(SecretKey.LENGTH));
} }
public static LocalAuthor getLocalAuthor() { public static Identity getIdentity() {
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH)); LocalAuthor localAuthor = getLocalAuthor();
} byte[] handshakePub = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
byte[] handshakePriv = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
public static LocalAuthor getLocalAuthor(int nameLength) { return new Identity(localAuthor, handshakePub, handshakePriv,
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
timestamp); timestamp);
} }
public static Author getAuthor() { public static LocalAuthor getLocalAuthor() {
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH)); AuthorId id = new AuthorId(getRandomId());
int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH);
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey);
} }
public static Author getAuthor(int nameLength) { public static Author getAuthor() {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
int nameLength = 1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH);
String name = getRandomString(nameLength); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, FORMAT_VERSION, name, publicKey); return new Author(id, FORMAT_VERSION, name, publicKey);
@@ -163,15 +166,19 @@ public class TestUtils {
} }
public static Contact getContact() { public static Contact getContact() {
return getContact(getAuthor(), random.nextBoolean()); return getContact(getAuthor(), new AuthorId(getRandomId()),
random.nextBoolean());
} }
public static Contact getContact(Author a, boolean verified) { public static Contact getContact(Author remote, AuthorId local,
return getContact(getContactId(), a, verified); boolean verified) {
return getContact(getContactId(), remote, local, verified);
} }
public static Contact getContact(ContactId c, Author a, boolean verified) { public static Contact getContact(ContactId c, Author remote, AuthorId local,
return new Contact(c, a, getRandomString(MAX_AUTHOR_NAME_LENGTH), boolean verified) {
return new Contact(c, remote, local,
getRandomString(MAX_AUTHOR_NAME_LENGTH),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH), verified); getRandomBytes(MAX_PUBLIC_KEY_LENGTH), verified);
} }

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.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939', 'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'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.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
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.19' testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
signature 'org.codehaus.mojo.signature:java16:1.1@signature' signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }

View File

@@ -4,8 +4,8 @@ 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.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.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
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.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
@@ -161,8 +161,8 @@ class AccountManagerImpl implements AccountManager {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
if (hasDatabaseKey()) if (hasDatabaseKey())
throw new AssertionError("Already have a database key"); throw new AssertionError("Already have a database key");
LocalAuthor localAuthor = identityManager.createLocalAuthor(name); Identity identity = identityManager.createIdentity(name);
identityManager.registerLocalAuthor(localAuthor); identityManager.registerIdentity(identity);
SecretKey key = crypto.generateSecretKey(); SecretKey key = crypto.generateSecretKey();
if (!encryptAndStoreDatabaseKey(key, password)) return false; if (!encryptAndStoreDatabaseKey(key, password)) return false;
databaseKey = key; databaseKey = key;

View File

@@ -39,7 +39,8 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
@Override @Override
public Group createContactGroup(ClientId clientId, int majorVersion, public Group createContactGroup(ClientId clientId, int majorVersion,
Contact contact, AuthorId local) { Contact contact) {
AuthorId local = contact.getLocalAuthorId();
AuthorId remote = contact.getAuthor().getId(); AuthorId remote = contact.getAuthor().getId();
byte[] descriptor = createGroupDescriptor(local, remote); byte[] descriptor = createGroupDescriptor(local, remote);
return groupFactory.createGroup(clientId, majorVersion, descriptor); return groupFactory.createGroup(clientId, majorVersion, descriptor);

View File

@@ -293,7 +293,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
throws DbException { throws DbException {
return db.transactionWithResult(false, txn -> { return db.transactionWithResult(false, txn -> {
ContactId contactId = contactManager.addContact(txn, remoteAuthor, ContactId contactId = contactManager.addContact(txn, remoteAuthor,
masterKey, timestamp, alice, true, true); localAuthor.getId(), masterKey, timestamp, alice,
true, true);
transportPropertyManager.addRemoteProperties(txn, contactId, transportPropertyManager.addRemoteProperties(txn, contactId,
remoteProperties); remoteProperties);
return contactId; return contactId;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.contact; package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
@@ -8,6 +9,7 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
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;
@@ -19,45 +21,42 @@ import org.briarproject.bramble.api.transport.KeyManager;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
import static org.briarproject.bramble.util.StringUtils.toUtf8; import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class ContactManagerImpl implements ContactManager { class ContactManagerImpl implements ContactManager {
private static final int LINK_LENGTH = 64;
private static final String REMOTE_CONTACT_LINK = private static final String REMOTE_CONTACT_LINK =
"briar://" + getRandomBase32String(LINK_LENGTH); "briar://" + getRandomBase32String(BASE32_LINK_BYTES);
private static final Pattern LINK_REGEX =
Pattern.compile("(briar://)?([a-z2-7]{" + LINK_LENGTH + "})");
private final DatabaseComponent db; private final DatabaseComponent db;
private final KeyManager keyManager; private final KeyManager keyManager;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final PendingContactFactory pendingContactFactory;
private final List<ContactHook> hooks; private final List<ContactHook> hooks;
@Inject @Inject
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager, ContactManagerImpl(DatabaseComponent db, KeyManager keyManager,
IdentityManager identityManager) { IdentityManager identityManager,
PendingContactFactory pendingContactFactory) {
this.db = db; this.db = db;
this.keyManager = keyManager; this.keyManager = keyManager;
this.identityManager = identityManager; this.identityManager = identityManager;
this.pendingContactFactory = pendingContactFactory;
hooks = new CopyOnWriteArrayList<>(); hooks = new CopyOnWriteArrayList<>();
} }
@@ -67,10 +66,10 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public ContactId addContact(Transaction txn, Author a, SecretKey rootKey, public ContactId addContact(Transaction txn, Author remote, AuthorId local,
long timestamp, boolean alice, boolean verified, boolean active) SecretKey rootKey, long timestamp, boolean alice, boolean verified,
throws DbException { boolean active) throws DbException {
ContactId c = db.addContact(txn, a, verified); ContactId c = db.addContact(txn, remote, local, verified);
keyManager.addContact(txn, c, rootKey, timestamp, alice, active); keyManager.addContact(txn, c, rootKey, timestamp, alice, active);
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact); for (ContactHook hook : hooks) hook.addingContact(txn, contact);
@@ -78,63 +77,46 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public ContactId addContact(Transaction txn, Author a, boolean verified) public ContactId addContact(Transaction txn, Author remote, AuthorId local,
throws DbException { boolean verified) throws DbException {
ContactId c = db.addContact(txn, a, verified); ContactId c = db.addContact(txn, remote, local, verified);
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
for (ContactHook hook : hooks) hook.addingContact(txn, contact); for (ContactHook hook : hooks) hook.addingContact(txn, contact);
return c; return c;
} }
@Override @Override
public ContactId addContact(Author a, SecretKey rootKey, long timestamp, public ContactId addContact(Author remote, AuthorId local,
boolean alice, boolean verified, boolean active) SecretKey rootKey, long timestamp, boolean alice, boolean verified,
throws DbException { boolean active) throws DbException {
return db.transactionWithResult(false, txn -> return db.transactionWithResult(false, txn ->
addContact(txn, a, rootKey, timestamp, alice, addContact(txn, remote, local, rootKey, timestamp, alice,
verified, active)); verified, active));
} }
@Override @Override
public String getRemoteContactLink() { public String getHandshakeLink() {
// TODO replace with real implementation // TODO replace with real implementation
return REMOTE_CONTACT_LINK; return REMOTE_CONTACT_LINK;
} }
@SuppressWarnings("SameParameterValue") @Override
private static String getRandomBase32String(int length) { public PendingContact addPendingContact(String link, String alias)
Random random = new Random(); throws DbException, FormatException {
char[] c = new char[length]; PendingContact p =
for (int i = 0; i < length; i++) { pendingContactFactory.createPendingContact(link, alias);
int character = random.nextInt(32); db.transaction(false, txn -> db.addPendingContact(txn, p));
if (character < 26) c[i] = (char) ('a' + character); return p;
else c[i] = (char) ('2' + (character - 26));
}
return new String(c);
} }
@Override @Override
public boolean isValidRemoteContactLink(String link) { public Collection<PendingContact> getPendingContacts() throws DbException {
return LINK_REGEX.matcher(link).matches(); return db.transactionWithResult(true, db::getPendingContacts);
} }
@Override @Override
public PendingContact addRemoteContactRequest(String link, String alias) { public void removePendingContact(PendingContactId p) throws DbException {
// TODO replace with real implementation db.transaction(false, txn -> db.removePendingContact(txn, p));
PendingContactId id = new PendingContactId(link.getBytes());
return new PendingContact(id, new byte[MAX_PUBLIC_KEY_LENGTH], alias,
WAITING_FOR_CONNECTION, System.currentTimeMillis());
}
@Override
public Collection<PendingContact> getPendingContacts() {
// TODO replace with real implementation
return emptyList();
}
@Override
public void removePendingContact(PendingContact pendingContact) {
// TODO replace with real implementation
} }
@Override @Override
@@ -143,13 +125,23 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public Contact getContact(AuthorId a) throws DbException { public Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
return db.transactionWithResult(true, txn -> getContact(txn, a)); throws DbException {
return db.transactionWithResult(true, txn ->
getContact(txn, remoteAuthorId, localAuthorId));
} }
@Override @Override
public Contact getContact(Transaction txn, AuthorId a) throws DbException { public Contact getContact(Transaction txn, AuthorId remoteAuthorId,
return db.getContact(txn, a); AuthorId localAuthorId) throws DbException {
Collection<Contact> contacts =
db.getContactsByAuthorId(txn, remoteAuthorId);
for (Contact c : contacts) {
if (c.getLocalAuthorId().equals(localAuthorId)) {
return c;
}
}
throw new NoSuchContactException();
} }
@Override @Override
@@ -180,14 +172,16 @@ class ContactManagerImpl implements ContactManager {
} }
@Override @Override
public boolean contactExists(Transaction txn, AuthorId a) public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
throws DbException { AuthorId localAuthorId) throws DbException {
return db.containsContact(txn, a); return db.containsContact(txn, remoteAuthorId, localAuthorId);
} }
@Override @Override
public boolean contactExists(AuthorId a) throws DbException { public boolean contactExists(AuthorId remoteAuthorId,
return db.transactionWithResult(true, txn -> contactExists(txn, a)); AuthorId localAuthorId) throws DbException {
return db.transactionWithResult(true, txn ->
contactExists(txn, remoteAuthorId, localAuthorId));
} }
@Override @Override
@@ -209,12 +203,12 @@ class ContactManagerImpl implements ContactManager {
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.getId().equals(authorId)) if (localAuthor.getId().equals(authorId))
return new AuthorInfo(OURSELVES); return new AuthorInfo(OURSELVES);
if (db.containsContact(txn, authorId)) { Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
Contact c = db.getContact(txn, authorId); if (contacts.isEmpty()) return new AuthorInfo(UNKNOWN);
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias()); if (contacts.size() > 1) throw new AssertionError();
else return new AuthorInfo(UNVERIFIED, c.getAlias()); Contact c = contacts.iterator().next();
} if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
return new AuthorInfo(UNKNOWN); else return new AuthorInfo(UNVERIFIED, c.getAlias());
} }
} }

View File

@@ -28,4 +28,10 @@ public class ContactModule {
ContactExchangeTaskImpl contactExchangeTask) { ContactExchangeTaskImpl contactExchangeTask) {
return contactExchangeTask; return contactExchangeTask;
} }
@Provides
PendingContactFactory providePendingContactFactory(
PendingContactFactoryImpl pendingContactFactory) {
return pendingContactFactory;
}
} }

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.contact.PendingContact;
interface PendingContactFactory {
/**
* Creates a {@link PendingContact} from the given handshake link and alias.
*
* @throws UnsupportedVersionException If the link uses a format version
* that is not supported
* @throws FormatException If the link is invalid
*/
PendingContact createPendingContact(String link, String alias)
throws FormatException;
}

View File

@@ -0,0 +1,70 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32;
import java.security.GeneralSecurityException;
import java.util.regex.Matcher;
import javax.inject.Inject;
import static java.lang.System.arraycopy;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
class PendingContactFactoryImpl implements PendingContactFactory {
private final CryptoComponent crypto;
private final Clock clock;
@Inject
PendingContactFactoryImpl(CryptoComponent crypto, Clock clock) {
this.crypto = crypto;
this.clock = clock;
}
@Override
public PendingContact createPendingContact(String link, String alias)
throws FormatException {
PublicKey publicKey = parseHandshakeLink(link);
PendingContactId id = getPendingContactId(publicKey);
long timestamp = clock.currentTimeMillis();
return new PendingContact(id, publicKey.getEncoded(), alias,
WAITING_FOR_CONNECTION, timestamp);
}
private PublicKey parseHandshakeLink(String link) throws FormatException {
Matcher matcher = LINK_REGEX.matcher(link);
if (!matcher.find()) throw new FormatException();
// Discard 'briar://' and anything before or after the link
link = matcher.group(2);
byte[] base32 = Base32.decode(link, false);
if (base32.length != RAW_LINK_BYTES) throw new AssertionError();
byte version = base32[0];
if (version != FORMAT_VERSION)
throw new UnsupportedVersionException(version < FORMAT_VERSION);
byte[] publicKeyBytes = new byte[base32.length - 1];
arraycopy(base32, 1, publicKeyBytes, 0, publicKeyBytes.length);
try {
KeyParser parser = crypto.getAgreementKeyParser();
return parser.parsePublicKey(publicKeyBytes);
} catch (GeneralSecurityException e) {
throw new FormatException();
}
}
private PendingContactId getPendingContactId(PublicKey publicKey) {
byte[] hash = crypto.hash(ID_LABEL, publicKey.getEncoded());
return new PendingContactId(hash);
}
}

View File

@@ -15,7 +15,7 @@ import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
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;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.Identity;
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.plugin.TransportId;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -87,10 +87,11 @@ interface Database<T> {
void commitTransaction(T txn) throws DbException; void commitTransaction(T txn) throws DbException;
/** /**
* Stores a contact with the given pseudonym and returns an ID for the * Stores a contact associated with the given local and remote pseudonyms,
* contact. * and returns an ID for the contact.
*/ */
ContactId addContact(T txn, Author a, boolean verified) throws DbException; ContactId addContact(T txn, Author remote, AuthorId local, boolean verified)
throws DbException;
/** /**
* Stores a group. * Stores a group.
@@ -119,9 +120,9 @@ interface Database<T> {
HandshakeKeys k) throws DbException; HandshakeKeys k) throws DbException;
/** /**
* Stores a local pseudonym. * Stores an identity.
*/ */
void addLocalAuthor(T txn, LocalAuthor a) throws DbException; void addIdentity(T txn, Identity i) throws DbException;
/** /**
* Stores a message. * Stores a message.
@@ -163,11 +164,13 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns true if the database contains the given contact. * Returns true if the database contains the given contact for the given
* local pseudonym.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
boolean containsContact(T txn, AuthorId a) throws DbException; boolean containsContact(T txn, AuthorId remote, AuthorId local)
throws DbException;
/** /**
* Returns true if the database contains the given contact. * Returns true if the database contains the given contact.
@@ -184,11 +187,12 @@ interface Database<T> {
boolean containsGroup(T txn, GroupId g) throws DbException; boolean containsGroup(T txn, GroupId g) throws DbException;
/** /**
* Returns true if the database contains the given local pseudonym. * Returns true if the database contains an identity for the given
* pseudonym.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
boolean containsLocalAuthor(T txn, AuthorId a) throws DbException; boolean containsIdentity(T txn, AuthorId a) throws DbException;
/** /**
* Returns true if the database contains the given message. * Returns true if the database contains the given message.
@@ -249,13 +253,6 @@ interface Database<T> {
*/ */
Contact getContact(T txn, ContactId c) throws DbException; Contact getContact(T txn, ContactId c) throws DbException;
/**
* Returns the contact with the given author ID.
* <p/>
* Read-only.
*/
Contact getContact(T txn, AuthorId a) throws DbException;
/** /**
* Returns all contacts. * Returns all contacts.
* <p/> * <p/>
@@ -263,6 +260,21 @@ interface Database<T> {
*/ */
Collection<Contact> getContacts(T txn) throws DbException; Collection<Contact> getContacts(T txn) throws DbException;
/**
* Returns a possibly empty collection of contacts with the given author ID.
* <p/>
* Read-only.
*/
Collection<Contact> getContactsByAuthorId(T txn, AuthorId remote)
throws DbException;
/**
* Returns all contacts associated with the given local pseudonym.
* <p/>
* Read-only.
*/
Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
/** /**
* Returns the group with the given ID. * Returns the group with the given ID.
* <p/> * <p/>
@@ -312,18 +324,18 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the local pseudonym with the given ID. * Returns the identity for local pseudonym with the given ID.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException; Identity getIdentity(T txn, AuthorId a) throws DbException;
/** /**
* Returns all local pseudonyms. * Returns the identities for all local pseudonyms.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException; Collection<Identity> getIdentities(T txn) throws DbException;
/** /**
* Returns the message with the given ID. * Returns the message with the given ID.
@@ -618,9 +630,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Removes a local pseudonym (and all associated state) from the database. * Removes an identity (and all associated state) from the database.
*/ */
void removeLocalAuthor(T txn, AuthorId a) throws DbException; void removeIdentity(T txn, AuthorId a) throws DbException;
/** /**
* Removes a message (and all associated state) from the database. * Removes a message (and all associated state) from the database.
@@ -674,6 +686,12 @@ interface Database<T> {
void setGroupVisibility(T txn, ContactId c, GroupId g, boolean shared) void setGroupVisibility(T txn, ContactId c, GroupId g, boolean shared)
throws DbException; throws DbException;
/**
* Sets the handshake key pair for the identity with the given ID.
*/
void setHandshakeKeyPair(T txn, AuthorId local, byte[] publicKey,
byte[] privateKey) throws DbException;
/** /**
* Marks the given message as shared. * Marks the given message as shared.
*/ */

View File

@@ -20,7 +20,7 @@ import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchIdentityException;
import org.briarproject.bramble.api.db.NoSuchMessageException; import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchPendingContactException;
import org.briarproject.bramble.api.db.NoSuchTransportException; import org.briarproject.bramble.api.db.NoSuchTransportException;
@@ -32,9 +32,9 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.event.EventExecutor;
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;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.event.LocalAuthorAddedEvent; import org.briarproject.bramble.api.identity.event.IdentityAddedEvent;
import org.briarproject.bramble.api.identity.event.LocalAuthorRemovedEvent; import org.briarproject.bramble.api.identity.event.IdentityRemovedEvent;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
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.plugin.TransportId;
@@ -232,15 +232,18 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public ContactId addContact(Transaction transaction, Author a, public ContactId addContact(Transaction transaction, Author remote,
boolean verified) throws DbException { AuthorId local, boolean verified)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (db.containsLocalAuthor(txn, a.getId())) if (!db.containsIdentity(txn, local))
throw new NoSuchIdentityException();
if (db.containsIdentity(txn, remote.getId()))
throw new ContactExistsException(); throw new ContactExistsException();
if (db.containsContact(txn, a.getId())) if (db.containsContact(txn, remote.getId(), local))
throw new ContactExistsException(); throw new ContactExistsException();
ContactId c = db.addContact(txn, a, verified); ContactId c = db.addContact(txn, remote, local, verified);
transaction.attach(new ContactAddedEvent(c)); transaction.attach(new ContactAddedEvent(c));
return c; return c;
} }
@@ -280,13 +283,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void addLocalAuthor(Transaction transaction, LocalAuthor a) public void addIdentity(Transaction transaction, Identity i)
throws DbException { throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, a.getId())) { if (!db.containsIdentity(txn, i.getId())) {
db.addLocalAuthor(txn, a); db.addIdentity(txn, i);
transaction.attach(new LocalAuthorAddedEvent(a.getId())); transaction.attach(new IdentityAddedEvent(i.getId()));
} }
} }
@@ -339,10 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId a) public boolean containsContact(Transaction transaction, AuthorId remote,
throws DbException { AuthorId local) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.containsContact(txn, a); if (!db.containsIdentity(txn, local))
throw new NoSuchIdentityException();
return db.containsContact(txn, remote, local);
} }
@Override @Override
@@ -353,10 +358,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean containsLocalAuthor(Transaction transaction, AuthorId local) public boolean containsIdentity(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.containsLocalAuthor(txn, local); return db.containsIdentity(txn, a);
} }
@Override @Override
@@ -482,15 +487,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getContact(txn, c); return db.getContact(txn, c);
} }
@Override
public Contact getContact(Transaction transaction, AuthorId a)
throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, a))
throw new NoSuchContactException();
return db.getContact(txn, a);
}
@Override @Override
public Collection<Contact> getContacts(Transaction transaction) public Collection<Contact> getContacts(Transaction transaction)
throws DbException { throws DbException {
@@ -498,6 +494,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getContacts(txn); return db.getContacts(txn);
} }
@Override
public Collection<Contact> getContactsByAuthorId(Transaction transaction,
AuthorId remote) throws DbException {
T txn = unbox(transaction);
return db.getContactsByAuthorId(txn, remote);
}
@Override
public Collection<ContactId> getContacts(Transaction transaction,
AuthorId a) throws DbException {
T txn = unbox(transaction);
if (!db.containsIdentity(txn, a))
throw new NoSuchIdentityException();
return db.getContacts(txn, a);
}
@Override @Override
public Group getGroup(Transaction transaction, GroupId g) public Group getGroup(Transaction transaction, GroupId g)
throws DbException { throws DbException {
@@ -542,19 +554,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public LocalAuthor getLocalAuthor(Transaction transaction, AuthorId a) public Identity getIdentity(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, a)) if (!db.containsIdentity(txn, a))
throw new NoSuchLocalAuthorException(); throw new NoSuchIdentityException();
return db.getLocalAuthor(txn, a); return db.getIdentity(txn, a);
} }
@Override @Override
public Collection<LocalAuthor> getLocalAuthors(Transaction transaction) public Collection<Identity> getIdentities(Transaction transaction)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getLocalAuthors(txn); return db.getIdentities(txn);
} }
@Override @Override
@@ -893,14 +905,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public void removeLocalAuthor(Transaction transaction, AuthorId a) public void removeIdentity(Transaction transaction, AuthorId a)
throws DbException { throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsLocalAuthor(txn, a)) if (!db.containsIdentity(txn, a))
throw new NoSuchLocalAuthorException(); throw new NoSuchIdentityException();
db.removeLocalAuthor(txn, a); db.removeIdentity(txn, a);
transaction.attach(new LocalAuthorRemovedEvent(a)); transaction.attach(new IdentityRemovedEvent(a));
} }
@Override @Override
@@ -1023,6 +1035,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
} }
@Override
public void setHandshakeKeyPair(Transaction transaction, AuthorId local,
byte[] publicKey, byte[] privateKey) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsIdentity(txn, local))
throw new NoSuchIdentityException();
db.setHandshakeKeyPair(txn, local, publicKey, privateKey);
}
@Override @Override
public void setReorderingWindow(Transaction transaction, public void setReorderingWindow(Transaction transaction,
TransportKeySetId k, TransportId t, long timePeriod, long base, TransportKeySetId k, TransportId t, long timePeriod, long base,

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
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;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
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.plugin.TransportId;
@@ -92,7 +93,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 = 44; static final int CODE_SCHEMA_VERSION = 43;
// 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;
@@ -127,8 +128,12 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " alias _STRING," // Null if no alias has been set + " alias _STRING," // Null if no alias has been set
+ " publicKey _BINARY NOT NULL," + " publicKey _BINARY NOT NULL,"
+ " handshakePublicKey _BINARY," // Null if key is unknown + " handshakePublicKey _BINARY," // Null if key is unknown
+ " localAuthorId _HASH NOT NULL,"
+ " verified BOOLEAN NOT NULL," + " verified BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId))"; + " PRIMARY KEY (contactId),"
+ " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_GROUPS = private static final String CREATE_GROUPS =
"CREATE TABLE groups" "CREATE TABLE groups"
@@ -482,8 +487,7 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration39_40(), new Migration39_40(),
new Migration40_41(dbTypes), new Migration40_41(dbTypes),
new Migration41_42(dbTypes), new Migration41_42(dbTypes),
new Migration42_43(dbTypes), new Migration42_43(dbTypes)
new Migration43_44()
); );
} }
@@ -659,21 +663,23 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public ContactId addContact(Connection txn, Author a, boolean verified) public ContactId addContact(Connection txn, Author remote, AuthorId local,
throws DbException { boolean verified) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
// Create a contact row // Create a contact row
String sql = "INSERT INTO contacts" String sql = "INSERT INTO contacts"
+ " (authorId, formatVersion, name, publicKey, verified)" + " (authorId, formatVersion, name, publicKey,"
+ " VALUES (?, ?, ?, ?, ?)"; + " localAuthorId, verified)"
+ " VALUES (?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getId().getBytes()); ps.setBytes(1, remote.getId().getBytes());
ps.setInt(2, a.getFormatVersion()); ps.setInt(2, remote.getFormatVersion());
ps.setString(3, a.getName()); ps.setString(3, remote.getName());
ps.setBytes(4, a.getPublicKey()); ps.setBytes(4, remote.getPublicKey());
ps.setBoolean(5, verified); ps.setBytes(5, local.getBytes());
ps.setBoolean(6, verified);
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -869,8 +875,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void addLocalAuthor(Connection txn, LocalAuthor a) public void addIdentity(Connection txn, Identity i) throws DbException {
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO localAuthors" String sql = "INSERT INTO localAuthors"
@@ -878,16 +883,17 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " handshakePublicKey, handshakePrivateKey, created)" + " handshakePublicKey, handshakePrivateKey, created)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getId().getBytes()); LocalAuthor local = i.getLocalAuthor();
ps.setInt(2, a.getFormatVersion()); ps.setBytes(1, local.getId().getBytes());
ps.setString(3, a.getName()); ps.setInt(2, local.getFormatVersion());
ps.setBytes(4, a.getPublicKey()); ps.setString(3, local.getName());
ps.setBytes(5, a.getPrivateKey()); ps.setBytes(4, local.getPublicKey());
if (a.getHandshakePublicKey() == null) ps.setNull(6, BINARY); ps.setBytes(5, local.getPrivateKey());
else ps.setBytes(6, a.getHandshakePublicKey()); if (i.getHandshakePublicKey() == null) ps.setNull(6, BINARY);
if (a.getHandshakePrivateKey() == null) ps.setNull(7, BINARY); else ps.setBytes(6, i.getHandshakePublicKey());
else ps.setBytes(7, a.getHandshakePrivateKey()); if (i.getHandshakePrivateKey() == null) ps.setNull(7, BINARY);
ps.setLong(8, a.getTimeCreated()); else ps.setBytes(7, i.getHandshakePrivateKey());
ps.setLong(8, i.getTimeCreated());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -1175,14 +1181,16 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public boolean containsContact(Connection txn, AuthorId a) public boolean containsContact(Connection txn, AuthorId remote,
throws DbException { AuthorId local) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT NULL FROM contacts WHERE authorId = ?"; String sql = "SELECT NULL FROM contacts"
+ " WHERE authorId = ? AND localAuthorId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes()); ps.setBytes(1, remote.getBytes());
ps.setBytes(2, local.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
boolean found = rs.next(); boolean found = rs.next();
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
@@ -1241,7 +1249,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public boolean containsLocalAuthor(Connection txn, AuthorId a) public boolean containsIdentity(Connection txn, AuthorId a)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -1425,7 +1433,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT authorId, formatVersion, name, alias," String sql = "SELECT authorId, formatVersion, name, alias,"
+ " publicKey, handshakePublicKey, verified" + " publicKey, handshakePublicKey, localAuthorId, verified"
+ " FROM contacts" + " FROM contacts"
+ " WHERE contactId = ?"; + " WHERE contactId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
@@ -1438,44 +1446,14 @@ abstract class JdbcDatabase implements Database<Connection> {
String alias = rs.getString(4); String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5); byte[] publicKey = rs.getBytes(5);
byte[] handshakePublicKey = rs.getBytes(6); byte[] handshakePublicKey = rs.getBytes(6);
boolean verified = rs.getBoolean(7); AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8);
rs.close(); rs.close();
ps.close(); ps.close();
Author author = Author author =
new Author(authorId, formatVersion, name, publicKey); new Author(authorId, formatVersion, name, publicKey);
return new Contact(c, author, alias, handshakePublicKey, verified); return new Contact(c, author, localAuthorId, alias,
} catch (SQLException e) { handshakePublicKey, verified);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public Contact getContact(Connection txn, AuthorId a) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId, formatVersion, name, alias,"
+ " publicKey, handshakePublicKey, verified"
+ " FROM contacts"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
ContactId contactId = new ContactId(rs.getInt(1));
int formatVersion = rs.getInt(2);
String name = rs.getString(3);
String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5);
byte[] handshakePublicKey = rs.getBytes(6);
boolean verified = rs.getBoolean(7);
rs.close();
ps.close();
Author author = new Author(a, formatVersion, name, publicKey);
return new Contact(contactId, author, alias, handshakePublicKey,
verified);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -1489,7 +1467,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT contactId, authorId, formatVersion, name," String sql = "SELECT contactId, authorId, formatVersion, name,"
+ " alias, publicKey, handshakePublicKey, verified" + " alias, publicKey, handshakePublicKey, localAuthorId,"
+ " verified"
+ " FROM contacts"; + " FROM contacts";
s = txn.createStatement(); s = txn.createStatement();
rs = s.executeQuery(sql); rs = s.executeQuery(sql);
@@ -1502,11 +1481,12 @@ abstract class JdbcDatabase implements Database<Connection> {
String alias = rs.getString(5); String alias = rs.getString(5);
byte[] publicKey = rs.getBytes(6); byte[] publicKey = rs.getBytes(6);
byte[] handshakePublicKey = rs.getBytes(7); byte[] handshakePublicKey = rs.getBytes(7);
boolean verified = rs.getBoolean(8); AuthorId localAuthorId = new AuthorId(rs.getBytes(8));
boolean verified = rs.getBoolean(9);
Author author = Author author =
new Author(authorId, formatVersion, name, publicKey); new Author(authorId, formatVersion, name, publicKey);
contacts.add(new Contact(contactId, author, alias, contacts.add(new Contact(contactId, author, localAuthorId,
handshakePublicKey, verified)); alias, handshakePublicKey, verified));
} }
rs.close(); rs.close();
s.close(); s.close();
@@ -1518,6 +1498,67 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Collection<ContactId> getContacts(Connection txn, AuthorId local)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId FROM contacts"
+ " WHERE localAuthorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, local.getBytes());
rs = ps.executeQuery();
List<ContactId> ids = new ArrayList<>();
while (rs.next()) ids.add(new ContactId(rs.getInt(1)));
rs.close();
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public Collection<Contact> getContactsByAuthorId(Connection txn,
AuthorId remote) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT contactId, formatVersion, name, alias,"
+ " publicKey, handshakePublicKey, localAuthorId, verified"
+ " FROM contacts"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, remote.getBytes());
rs = ps.executeQuery();
List<Contact> contacts = new ArrayList<>();
while (rs.next()) {
ContactId contactId = new ContactId(rs.getInt(1));
int formatVersion = rs.getInt(2);
String name = rs.getString(3);
String alias = rs.getString(4);
byte[] publicKey = rs.getBytes(5);
byte[] handshakePublicKey = rs.getBytes(6);
AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
boolean verified = rs.getBoolean(8);
Author author =
new Author(remote, formatVersion, name, publicKey);
contacts.add(new Contact(contactId, author, localAuthorId,
alias, handshakePublicKey, verified));
}
rs.close();
ps.close();
return contacts;
} 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;
@@ -1620,41 +1661,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public LocalAuthor getLocalAuthor(Connection txn, AuthorId a)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT formatVersion, name, publicKey, privateKey,"
+ " handshakePublicKey, handshakePrivateKey, created"
+ " FROM localAuthors"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
int formatVersion = rs.getInt(1);
String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3);
byte[] privateKey = rs.getBytes(4);
byte[] handshakePublicKey = rs.getBytes(5);
byte[] handshakePrivateKey = rs.getBytes(6);
long created = rs.getLong(7);
LocalAuthor localAuthor = new LocalAuthor(a, formatVersion, name,
publicKey, privateKey, handshakePublicKey,
handshakePrivateKey, created);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return localAuthor;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<HandshakeKeySet> getHandshakeKeys(Connection txn, public Collection<HandshakeKeySet> getHandshakeKeys(Connection txn,
TransportId t) throws DbException { TransportId t) throws DbException {
@@ -1736,30 +1742,69 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<LocalAuthor> getLocalAuthors(Connection txn) public Identity getIdentity(Connection txn, AuthorId a) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT formatVersion, name, publicKey, privateKey,"
+ " handshakePublicKey, handshakePrivateKey, created"
+ " FROM localAuthors"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
int formatVersion = rs.getInt(1);
String name = rs.getString(2);
byte[] publicKey = rs.getBytes(3);
byte[] privateKey = rs.getBytes(4);
byte[] handshakePublicKey = rs.getBytes(5);
byte[] handshakePrivateKey = rs.getBytes(6);
long created = rs.getLong(7);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
LocalAuthor local = new LocalAuthor(a, formatVersion, name,
publicKey, privateKey);
return new Identity(local, handshakePublicKey, handshakePrivateKey,
created);
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public Collection<Identity> getIdentities(Connection txn)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT authorId, formatVersion, name, publicKey," String sql = "SELECT authorId, formatVersion, name, publicKey,"
+ " privateKey, created" + " privateKey, handshakePublicKey, handshakePrivateKey,"
+ " created"
+ " FROM localAuthors"; + " FROM localAuthors";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<LocalAuthor> authors = new ArrayList<>(); List<Identity> identities = new ArrayList<>();
while (rs.next()) { while (rs.next()) {
AuthorId authorId = new AuthorId(rs.getBytes(1)); AuthorId authorId = new AuthorId(rs.getBytes(1));
int formatVersion = rs.getInt(2); int formatVersion = rs.getInt(2);
String name = rs.getString(3); String name = rs.getString(3);
byte[] publicKey = rs.getBytes(4); byte[] publicKey = rs.getBytes(4);
byte[] privateKey = rs.getBytes(5); byte[] privateKey = rs.getBytes(5);
long created = rs.getLong(6); byte[] handshakePublicKey = rs.getBytes(6);
authors.add(new LocalAuthor(authorId, formatVersion, name, byte[] handshakePrivateKey = rs.getBytes(7);
publicKey, privateKey, created)); long created = rs.getLong(8);
LocalAuthor local = new LocalAuthor(authorId, formatVersion,
name, publicKey, privateKey);
identities.add(new Identity(local, handshakePublicKey,
handshakePrivateKey, created));
} }
rs.close(); rs.close();
ps.close(); ps.close();
return authors; return identities;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
@@ -2918,8 +2963,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void removeLocalAuthor(Connection txn, AuthorId a) public void removeIdentity(Connection txn, AuthorId a) throws DbException {
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "DELETE FROM localAuthors WHERE authorId = ?"; String sql = "DELETE FROM localAuthors WHERE authorId = ?";
@@ -3136,6 +3180,27 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void setHandshakeKeyPair(Connection txn, AuthorId local,
byte[] publicKey, byte[] privateKey) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE localAuthors"
+ " SET handshakePublicKey = ?, handshakePrivateKey = ?"
+ " WHERE authorId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, publicKey);
ps.setBytes(2, privateKey);
ps.setBytes(3, local.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 {

View File

@@ -1,40 +0,0 @@
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 Migration43_44 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration43_44.class.getName());
@Override
public int getStartVersion() {
return 43;
}
@Override
public int getEndVersion() {
return 44;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
s.execute("ALTER TABLE contacts"
+ " DROP COLUMN localAuthorId");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -1,12 +1,12 @@
package org.briarproject.bramble.identity; package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -22,12 +22,10 @@ import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
class AuthorFactoryImpl implements AuthorFactory { class AuthorFactoryImpl implements AuthorFactory {
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final Clock clock;
@Inject @Inject
AuthorFactoryImpl(CryptoComponent crypto, Clock clock) { AuthorFactoryImpl(CryptoComponent crypto) {
this.crypto = crypto; this.crypto = crypto;
this.clock = clock;
} }
@Override @Override
@@ -43,17 +41,12 @@ class AuthorFactoryImpl implements AuthorFactory {
} }
@Override @Override
public LocalAuthor createLocalAuthor(String name, byte[] publicKey, public LocalAuthor createLocalAuthor(String name) {
byte[] privateKey) { KeyPair signatureKeyPair = crypto.generateSignatureKeyPair();
return createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey); byte[] publicKey = signatureKeyPair.getPublic().getEncoded();
} byte[] privateKey = signatureKeyPair.getPrivate().getEncoded();
AuthorId id = getId(FORMAT_VERSION, name, publicKey);
@Override return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey);
public LocalAuthor createLocalAuthor(int formatVersion, String name,
byte[] publicKey, byte[] privateKey) {
AuthorId id = getId(formatVersion, name, publicKey);
return new LocalAuthor(id, formatVersion, name, publicKey, privateKey,
clock.currentTimeMillis());
} }
private AuthorId getId(int formatVersion, String name, byte[] publicKey) { private AuthorId getId(int formatVersion, String name, byte[] publicKey) {

View File

@@ -6,97 +6,164 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.util.Collection;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
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;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class IdentityManagerImpl implements IdentityManager { class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(IdentityManagerImpl.class.getName()); getLogger(IdentityManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final AuthorFactory authorFactory; private final AuthorFactory authorFactory;
private final Clock clock;
// The local author is immutable so we can cache it /**
* The user's identity, or null if no identity has been registered or
* loaded. If non-null, this identity always has handshake keys.
*/
@Nullable @Nullable
private volatile LocalAuthor cachedAuthor; private volatile Identity cachedIdentity = null;
/**
* True if {@code cachedIdentity} was registered via
* {@link #registerIdentity(Identity)} and should be stored when
* {@link #onDatabaseOpened(Transaction)} is called.
*/
private volatile boolean shouldStoreIdentity = false;
/**
* True if the handshake keys in {@code cachedIdentity} were generated
* when the identity was loaded and should be stored when
* {@link #onDatabaseOpened(Transaction)} is called.
*/
private volatile boolean shouldStoreKeys = false;
@Inject @Inject
IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto, IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto,
AuthorFactory authorFactory) { AuthorFactory authorFactory, Clock clock) {
this.db = db; this.db = db;
this.crypto = crypto; this.crypto = crypto;
this.authorFactory = authorFactory; this.authorFactory = authorFactory;
this.clock = clock;
} }
@Override @Override
public LocalAuthor createLocalAuthor(String name) { public Identity createIdentity(String name) {
long start = now(); long start = now();
KeyPair keyPair = crypto.generateSignatureKeyPair(); LocalAuthor localAuthor = authorFactory.createLocalAuthor(name);
byte[] publicKey = keyPair.getPublic().getEncoded(); KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair();
byte[] privateKey = keyPair.getPrivate().getEncoded(); byte[] handshakePub = handshakeKeyPair.getPublic().getEncoded();
LocalAuthor localAuthor = authorFactory.createLocalAuthor(name, byte[] handshakePriv = handshakeKeyPair.getPrivate().getEncoded();
publicKey, privateKey); logDuration(LOG, "Creating identity", start);
logDuration(LOG, "Creating local author", start); return new Identity(localAuthor, handshakePub, handshakePriv,
return localAuthor; clock.currentTimeMillis());
} }
@Override @Override
public void registerLocalAuthor(LocalAuthor a) { public void registerIdentity(Identity i) {
cachedAuthor = a; if (!i.hasHandshakeKeyPair()) throw new IllegalArgumentException();
LOG.info("Local author registered"); cachedIdentity = i;
shouldStoreIdentity = true;
LOG.info("Identity registered");
} }
@Override @Override
public void storeLocalAuthor() throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
LocalAuthor cached = cachedAuthor; Identity cached = getCachedIdentity(txn);
if (cached == null) { if (shouldStoreIdentity) {
LOG.info("No local author to store"); // The identity was registered at startup - store it
return; db.addIdentity(txn, cached);
LOG.info("Identity stored");
} else if (shouldStoreKeys) {
// Handshake keys were generated when loading the identity -
// store them
byte[] handshakePub =
requireNonNull(cached.getHandshakePublicKey());
byte[] handshakePriv =
requireNonNull(cached.getHandshakePrivateKey());
db.setHandshakeKeyPair(txn, cached.getId(), handshakePub,
handshakePriv);
LOG.info("Handshake key pair stored");
} }
db.transaction(false, txn -> db.addLocalAuthor(txn, cached));
LOG.info("Local author stored");
} }
@Override @Override
public LocalAuthor getLocalAuthor() throws DbException { public LocalAuthor getLocalAuthor() throws DbException {
if (cachedAuthor == null) { Identity cached = cachedIdentity;
cachedAuthor = if (cached == null)
db.transactionWithResult(true, this::loadLocalAuthor); cached = db.transactionWithResult(true, this::getCachedIdentity);
LOG.info("Local author loaded"); return cached.getLocalAuthor();
}
LocalAuthor cached = cachedAuthor;
if (cached == null) throw new AssertionError();
return cached;
} }
@Override @Override
public LocalAuthor getLocalAuthor(Transaction txn) throws DbException { public LocalAuthor getLocalAuthor(Transaction txn) throws DbException {
if (cachedAuthor == null) { return getCachedIdentity(txn).getLocalAuthor();
cachedAuthor = loadLocalAuthor(txn); }
LOG.info("Local author loaded");
} @Override
LocalAuthor cached = cachedAuthor; public byte[][] getHandshakeKeys(Transaction txn) throws DbException {
if (cached == null) throw new AssertionError(); Identity cached = getCachedIdentity(txn);
return new byte[][] {
cached.getHandshakePublicKey(),
cached.getHandshakePrivateKey()
};
}
/**
* Loads the identity if necessary and returns it. If
* {@code cachedIdentity} was not already set by calling
* {@link #registerIdentity(Identity)}, this method sets it. If
* {@code cachedIdentity} was already set, either by calling
* {@link #registerIdentity(Identity)} or by a previous call to this
* method, then this method returns the cached identity without hitting
* the database.
*/
private Identity getCachedIdentity(Transaction txn) throws DbException {
Identity cached = cachedIdentity;
if (cached == null)
cachedIdentity = cached = loadIdentityWithKeyPair(txn);
return cached; return cached;
} }
private LocalAuthor loadLocalAuthor(Transaction txn) throws DbException { /**
return db.getLocalAuthors(txn).iterator().next(); * Loads and returns the identity, generating a handshake key pair if
* necessary and setting {@code shouldStoreKeys} if a handshake key pair
* was generated.
*/
private Identity loadIdentityWithKeyPair(Transaction txn)
throws DbException {
Collection<Identity> identities = db.getIdentities(txn);
if (identities.size() != 1) throw new DbException();
Identity i = identities.iterator().next();
LOG.info("Identity loaded");
if (i.hasHandshakeKeyPair()) return i;
KeyPair handshakeKeyPair = crypto.generateAgreementKeyPair();
byte[] handshakePub = handshakeKeyPair.getPublic().getEncoded();
byte[] handshakePriv = handshakeKeyPair.getPrivate().getEncoded();
LOG.info("Handshake key pair generated");
shouldStoreKeys = true;
return new Identity(i.getLocalAuthor(), handshakePub, handshakePriv,
i.getTimeCreated());
} }
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -24,8 +25,9 @@ public class IdentityModule {
@Provides @Provides
@Singleton @Singleton
IdentityManager provideIdentityManager( IdentityManager provideIdentityManager(LifecycleManager lifecycleManager,
IdentityManagerImpl identityManager) { IdentityManagerImpl identityManager) {
lifecycleManager.registerOpenDatabaseHook(identityManager);
return identityManager; return identityManager;
} }
} }

View File

@@ -1,13 +1,13 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadParser; import org.briarproject.bramble.api.keyagreement.PayloadParser;
import org.briarproject.bramble.api.keyagreement.TransportDescriptor; import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;

View File

@@ -7,13 +7,11 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@@ -28,6 +26,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
@@ -49,14 +48,13 @@ import static org.briarproject.bramble.util.LogUtils.now;
class LifecycleManagerImpl implements LifecycleManager, MigrationListener { class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LifecycleManagerImpl.class.getName()); getLogger(LifecycleManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final EventBus eventBus; private final EventBus eventBus;
private final List<Service> services; private final List<Service> services;
private final List<Client> clients; private final List<OpenDatabaseHook> openDatabaseHooks;
private final List<ExecutorService> executors; private final List<ExecutorService> executors;
private final IdentityManager identityManager;
private final Semaphore startStopSemaphore = new Semaphore(1); private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1); private final CountDownLatch dbLatch = new CountDownLatch(1);
private final CountDownLatch startupLatch = new CountDownLatch(1); private final CountDownLatch startupLatch = new CountDownLatch(1);
@@ -65,13 +63,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private volatile LifecycleState state = STARTING; private volatile LifecycleState state = STARTING;
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus) {
IdentityManager identityManager) {
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
this.identityManager = identityManager;
services = new CopyOnWriteArrayList<>(); services = new CopyOnWriteArrayList<>();
clients = new CopyOnWriteArrayList<>(); openDatabaseHooks = new CopyOnWriteArrayList<>();
executors = new CopyOnWriteArrayList<>(); executors = new CopyOnWriteArrayList<>();
} }
@@ -83,10 +79,12 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} }
@Override @Override
public void registerClient(Client c) { public void registerOpenDatabaseHook(OpenDatabaseHook hook) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO)) {
LOG.info("Registering client " + c.getClass().getSimpleName()); LOG.info("Registering open database hook "
clients.add(c); + hook.getClass().getSimpleName());
}
openDatabaseHooks.add(hook);
} }
@Override @Override
@@ -102,28 +100,28 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
return ALREADY_RUNNING; return ALREADY_RUNNING;
} }
try { try {
LOG.info("Starting services"); LOG.info("Opening database");
long start = now(); long start = now();
boolean reopened = db.open(dbKey, this); boolean reopened = db.open(dbKey, this);
if (reopened) logDuration(LOG, "Reopening database", start); if (reopened) logDuration(LOG, "Reopening database", start);
else logDuration(LOG, "Creating database", start); else logDuration(LOG, "Creating database", start);
identityManager.storeLocalAuthor();
db.transaction(false, txn -> {
for (OpenDatabaseHook hook : openDatabaseHooks) {
long start1 = now();
hook.onDatabaseOpened(txn);
if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Calling open database hook "
+ hook.getClass().getSimpleName(), start1);
}
}
});
LOG.info("Starting services");
state = STARTING_SERVICES; state = STARTING_SERVICES;
dbLatch.countDown(); dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES)); eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
db.transaction(false, txn -> {
for (Client c : clients) {
long start1 = now();
c.createLocalState(txn);
if (LOG.isLoggable(FINE)) {
logDuration(LOG, "Starting client "
+ c.getClass().getSimpleName(), start1);
}
}
});
for (Service s : services) { for (Service s : services) {
start = now(); start = now();
s.startService(); s.startService();

View File

@@ -8,7 +8,6 @@ import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
@@ -30,10 +29,7 @@ import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -42,7 +38,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -64,15 +59,11 @@ class PluginManagerImpl implements PluginManager, Service {
Logger.getLogger(PluginManagerImpl.class.getName()); Logger.getLogger(PluginManagerImpl.class.getName());
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final EventBus eventBus; private final EventBus eventBus;
private final PluginConfig pluginConfig; private final PluginConfig pluginConfig;
private final ConnectionManager connectionManager; private final ConnectionManager connectionManager;
private final ConnectionRegistry connectionRegistry;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final TransportPropertyManager transportPropertyManager; private final TransportPropertyManager transportPropertyManager;
private final SecureRandom random;
private final Clock clock;
private final Map<TransportId, Plugin> plugins; private final Map<TransportId, Plugin> plugins;
private final List<SimplexPlugin> simplexPlugins; private final List<SimplexPlugin> simplexPlugins;
private final List<DuplexPlugin> duplexPlugins; private final List<DuplexPlugin> duplexPlugins;
@@ -80,41 +71,25 @@ class PluginManagerImpl implements PluginManager, Service {
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@Inject @Inject
PluginManagerImpl(@IoExecutor Executor ioExecutor, PluginManagerImpl(@IoExecutor Executor ioExecutor, EventBus eventBus,
@Scheduler ScheduledExecutorService scheduler, EventBus eventBus,
PluginConfig pluginConfig, ConnectionManager connectionManager, PluginConfig pluginConfig, ConnectionManager connectionManager,
ConnectionRegistry connectionRegistry,
SettingsManager settingsManager, SettingsManager settingsManager,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager) {
SecureRandom random, Clock clock) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.eventBus = eventBus; this.eventBus = eventBus;
this.pluginConfig = pluginConfig; this.pluginConfig = pluginConfig;
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.connectionRegistry = connectionRegistry;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.transportPropertyManager = transportPropertyManager; this.transportPropertyManager = transportPropertyManager;
this.random = random;
this.clock = clock;
plugins = new ConcurrentHashMap<>(); plugins = new ConcurrentHashMap<>();
simplexPlugins = new CopyOnWriteArrayList<>(); simplexPlugins = new CopyOnWriteArrayList<>();
duplexPlugins = new CopyOnWriteArrayList<>(); duplexPlugins = new CopyOnWriteArrayList<>();
startLatches = new ConcurrentHashMap<>(); startLatches = new ConcurrentHashMap<>();
} }
@Override @Override
public void startService() { public void startService() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
// Instantiate the poller
if (pluginConfig.shouldPoll()) {
LOG.info("Starting poller");
Poller poller = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, this, transportPropertyManager, random,
clock);
eventBus.addListener(poller);
}
// Instantiate the simplex plugins and start them asynchronously // Instantiate the simplex plugins and start them asynchronously
LOG.info("Starting simplex plugins"); LOG.info("Starting simplex plugins");
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) { for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {

View File

@@ -1,9 +1,11 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import javax.inject.Inject; import javax.inject.Inject;
@@ -18,6 +20,8 @@ public class PluginModule {
public static class EagerSingletons { public static class EagerSingletons {
@Inject @Inject
PluginManager pluginManager; PluginManager pluginManager;
@Inject
Poller poller;
} }
@Provides @Provides
@@ -46,4 +50,12 @@ public class PluginModule {
lifecycleManager.registerService(pluginManager); lifecycleManager.registerService(pluginManager);
return pluginManager; return pluginManager;
} }
@Provides
@Singleton
Poller providePoller(PluginConfig config, EventBus eventBus,
PollerImpl poller) {
if (config.shouldPoll()) eventBus.addListener(poller);
return poller;
}
} }

View File

@@ -1,259 +1,7 @@
package org.briarproject.bramble.plugin; package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.contact.ContactId; /**
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; * Empty interface used for injecting the poller.
import org.briarproject.bramble.api.db.DbException; */
import org.briarproject.bramble.api.event.Event; interface Poller {
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class Poller implements EventListener {
private static final Logger LOG = Logger.getLogger(Poller.class.getName());
private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final ConnectionManager connectionManager;
private final ConnectionRegistry connectionRegistry;
private final PluginManager pluginManager;
private final TransportPropertyManager transportPropertyManager;
private final SecureRandom random;
private final Clock clock;
private final Lock lock;
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
Poller(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler,
ConnectionManager connectionManager,
ConnectionRegistry connectionRegistry, PluginManager pluginManager,
TransportPropertyManager transportPropertyManager,
SecureRandom random, Clock clock) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.connectionManager = connectionManager;
this.connectionRegistry = connectionRegistry;
this.pluginManager = pluginManager;
this.transportPropertyManager = transportPropertyManager;
this.random = random;
this.clock = clock;
lock = new ReentrantLock();
tasks = new HashMap<>();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
ContactAddedEvent c = (ContactAddedEvent) e;
// Connect to the newly activated contact
connectToContact(c.getContactId());
} else if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
if (!c.isIncoming()) {
// Connect to the disconnected contact
connectToContact(c.getContactId(), c.getTransportId());
}
} else if (e instanceof ConnectionOpenedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
} else if (e instanceof TransportEnabledEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e;
// Poll the newly enabled transport
pollNow(t.getTransportId());
} else if (e instanceof TransportDisabledEvent) {
TransportDisabledEvent t = (TransportDisabledEvent) e;
// Cancel polling for the disabled transport
cancel(t.getTransportId());
}
}
private void connectToContact(ContactId c) {
for (SimplexPlugin s : pluginManager.getSimplexPlugins())
if (s.shouldPoll()) connectToContact(c, s);
for (DuplexPlugin d : pluginManager.getDuplexPlugins())
if (d.shouldPoll()) connectToContact(c, d);
}
private void connectToContact(ContactId c, TransportId t) {
Plugin p = pluginManager.getPlugin(t);
if (p instanceof SimplexPlugin && p.shouldPoll())
connectToContact(c, (SimplexPlugin) p);
else if (p instanceof DuplexPlugin && p.shouldPoll())
connectToContact(c, (DuplexPlugin) p);
}
private void connectToContact(ContactId c, SimplexPlugin p) {
ioExecutor.execute(() -> {
TransportId t = p.getId();
if (connectionRegistry.isConnected(c, t)) return;
try {
TransportProperties props =
transportPropertyManager.getRemoteProperties(c, t);
TransportConnectionWriter w = p.createWriter(props);
if (w != null)
connectionManager.manageOutgoingConnection(c, t, w);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void connectToContact(ContactId c, DuplexPlugin p) {
ioExecutor.execute(() -> {
TransportId t = p.getId();
if (connectionRegistry.isConnected(c, t)) return;
try {
TransportProperties props =
transportPropertyManager.getRemoteProperties(c, t);
DuplexTransportConnection d = p.createConnection(props);
if (d != null)
connectionManager.manageOutgoingConnection(c, t, d);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void reschedule(TransportId t) {
Plugin p = pluginManager.getPlugin(t);
if (p != null && p.shouldPoll())
schedule(p, p.getPollingInterval(), false);
}
private void pollNow(TransportId t) {
Plugin p = pluginManager.getPlugin(t);
// Randomise next polling interval
if (p != null && p.shouldPoll()) schedule(p, 0, true);
}
private void schedule(Plugin p, int delay, boolean randomiseNext) {
// Replace any later scheduled task for this plugin
long due = clock.currentTimeMillis() + delay;
TransportId t = p.getId();
lock.lock();
try {
ScheduledPollTask scheduled = tasks.get(t);
if (scheduled == null || due < scheduled.task.due) {
// If a later task exists, cancel it. If it's already started
// it will abort safely when it finds it's been replaced
if (scheduled != null) scheduled.future.cancel(false);
PollTask task = new PollTask(p, due, randomiseNext);
Future future = scheduler.schedule(
() -> ioExecutor.execute(task), delay, MILLISECONDS);
tasks.put(t, new ScheduledPollTask(task, future));
}
} finally {
lock.unlock();
}
}
private void cancel(TransportId t) {
lock.lock();
try {
ScheduledPollTask scheduled = tasks.remove(t);
if (scheduled != null) scheduled.future.cancel(false);
} finally {
lock.unlock();
}
}
@IoExecutor
private void poll(Plugin p) {
TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
try {
Map<ContactId, TransportProperties> remote =
transportPropertyManager.getRemoteProperties(t);
Collection<ContactId> connected =
connectionRegistry.getConnectedContacts(t);
remote = new HashMap<>(remote);
remote.keySet().removeAll(connected);
if (!remote.isEmpty()) p.poll(remote);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private class ScheduledPollTask {
private final PollTask task;
private final Future future;
private ScheduledPollTask(PollTask task, Future future) {
this.task = task;
this.future = future;
}
}
private class PollTask implements Runnable {
private final Plugin plugin;
private final long due;
private final boolean randomiseNext;
private PollTask(Plugin plugin, long due, boolean randomiseNext) {
this.plugin = plugin;
this.due = due;
this.randomiseNext = randomiseNext;
}
@Override
@IoExecutor
public void run() {
lock.lock();
try {
TransportId t = plugin.getId();
ScheduledPollTask scheduled = tasks.get(t);
if (scheduled != null && scheduled.task != this)
return; // Replaced by another task
tasks.remove(t);
} finally {
lock.unlock();
}
int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false);
poll(plugin);
}
}
} }

View File

@@ -0,0 +1,264 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class PollerImpl implements Poller, EventListener {
private static final Logger LOG = getLogger(PollerImpl.class.getName());
private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final ConnectionManager connectionManager;
private final ConnectionRegistry connectionRegistry;
private final PluginManager pluginManager;
private final TransportPropertyManager transportPropertyManager;
private final SecureRandom random;
private final Clock clock;
private final Lock lock;
@GuardedBy("lock")
private final Map<TransportId, ScheduledPollTask> tasks;
@Inject
PollerImpl(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler,
ConnectionManager connectionManager,
ConnectionRegistry connectionRegistry, PluginManager pluginManager,
TransportPropertyManager transportPropertyManager,
SecureRandom random, Clock clock) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.connectionManager = connectionManager;
this.connectionRegistry = connectionRegistry;
this.pluginManager = pluginManager;
this.transportPropertyManager = transportPropertyManager;
this.random = random;
this.clock = clock;
lock = new ReentrantLock();
tasks = new HashMap<>();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
ContactAddedEvent c = (ContactAddedEvent) e;
// Connect to the newly added contact
connectToContact(c.getContactId());
} else if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
if (!c.isIncoming()) {
// Connect to the disconnected contact
connectToContact(c.getContactId(), c.getTransportId());
}
} else if (e instanceof ConnectionOpenedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
} else if (e instanceof TransportEnabledEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e;
// Poll the newly enabled transport
pollNow(t.getTransportId());
} else if (e instanceof TransportDisabledEvent) {
TransportDisabledEvent t = (TransportDisabledEvent) e;
// Cancel polling for the disabled transport
cancel(t.getTransportId());
}
}
private void connectToContact(ContactId c) {
for (SimplexPlugin s : pluginManager.getSimplexPlugins())
if (s.shouldPoll()) connectToContact(c, s);
for (DuplexPlugin d : pluginManager.getDuplexPlugins())
if (d.shouldPoll()) connectToContact(c, d);
}
private void connectToContact(ContactId c, TransportId t) {
Plugin p = pluginManager.getPlugin(t);
if (p instanceof SimplexPlugin && p.shouldPoll())
connectToContact(c, (SimplexPlugin) p);
else if (p instanceof DuplexPlugin && p.shouldPoll())
connectToContact(c, (DuplexPlugin) p);
}
private void connectToContact(ContactId c, SimplexPlugin p) {
ioExecutor.execute(() -> {
TransportId t = p.getId();
if (connectionRegistry.isConnected(c, t)) return;
try {
TransportProperties props =
transportPropertyManager.getRemoteProperties(c, t);
TransportConnectionWriter w = p.createWriter(props);
if (w != null)
connectionManager.manageOutgoingConnection(c, t, w);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void connectToContact(ContactId c, DuplexPlugin p) {
ioExecutor.execute(() -> {
TransportId t = p.getId();
if (connectionRegistry.isConnected(c, t)) return;
try {
TransportProperties props =
transportPropertyManager.getRemoteProperties(c, t);
DuplexTransportConnection d = p.createConnection(props);
if (d != null)
connectionManager.manageOutgoingConnection(c, t, d);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void reschedule(TransportId t) {
Plugin p = pluginManager.getPlugin(t);
if (p != null && p.shouldPoll())
schedule(p, p.getPollingInterval(), false);
}
private void pollNow(TransportId t) {
Plugin p = pluginManager.getPlugin(t);
// Randomise next polling interval
if (p != null && p.shouldPoll()) schedule(p, 0, true);
}
private void schedule(Plugin p, int delay, boolean randomiseNext) {
// Replace any later scheduled task for this plugin
long due = clock.currentTimeMillis() + delay;
TransportId t = p.getId();
lock.lock();
try {
ScheduledPollTask scheduled = tasks.get(t);
if (scheduled == null || due < scheduled.task.due) {
// If a later task exists, cancel it. If it's already started
// it will abort safely when it finds it's been replaced
if (scheduled != null) scheduled.future.cancel(false);
PollTask task = new PollTask(p, due, randomiseNext);
Future future = scheduler.schedule(() ->
ioExecutor.execute(task), delay, MILLISECONDS);
tasks.put(t, new ScheduledPollTask(task, future));
}
} finally {
lock.unlock();
}
}
private void cancel(TransportId t) {
lock.lock();
try {
ScheduledPollTask scheduled = tasks.remove(t);
if (scheduled != null) scheduled.future.cancel(false);
} finally {
lock.unlock();
}
}
@IoExecutor
private void poll(Plugin p) {
TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
try {
Map<ContactId, TransportProperties> remote =
transportPropertyManager.getRemoteProperties(t);
Collection<ContactId> connected =
connectionRegistry.getConnectedContacts(t);
remote = new HashMap<>(remote);
remote.keySet().removeAll(connected);
if (!remote.isEmpty()) p.poll(remote);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private class ScheduledPollTask {
private final PollTask task;
private final Future future;
private ScheduledPollTask(PollTask task, Future future) {
this.task = task;
this.future = future;
}
}
private class PollTask implements Runnable {
private final Plugin plugin;
private final long due;
private final boolean randomiseNext;
private PollTask(Plugin plugin, long due, boolean randomiseNext) {
this.plugin = plugin;
this.due = due;
this.randomiseNext = randomiseNext;
}
@Override
@IoExecutor
public void run() {
lock.lock();
try {
TransportId t = plugin.getId();
ScheduledPollTask scheduled = tasks.get(t);
if (scheduled != null && scheduled.task != this)
return; // Replaced by another task
tasks.remove(t);
} finally {
lock.unlock();
}
int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false);
poll(plugin);
}
}
}

View File

@@ -48,7 +48,7 @@ public class PropertiesModule {
ValidationManager validationManager, ContactManager contactManager, ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
TransportPropertyManagerImpl transportPropertyManager) { TransportPropertyManagerImpl transportPropertyManager) {
lifecycleManager.registerClient(transportPropertyManager); lifecycleManager.registerOpenDatabaseHook(transportPropertyManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
transportPropertyManager); transportPropertyManager);
contactManager.registerContactHook(transportPropertyManager); contactManager.registerContactHook(transportPropertyManager);

View File

@@ -13,13 +13,11 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.identity.IdentityManager;
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.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility; import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
@@ -42,11 +40,11 @@ import javax.inject.Inject;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class TransportPropertyManagerImpl implements TransportPropertyManager, class TransportPropertyManagerImpl implements TransportPropertyManager,
Client, ContactHook, ClientVersioningHook, IncomingMessageHook { OpenDatabaseHook, ContactHook, ClientVersioningHook,
IncomingMessageHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final IdentityManager identityManager;
private final ClientVersioningManager clientVersioningManager; private final ClientVersioningManager clientVersioningManager;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
@@ -56,13 +54,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Inject @Inject
TransportPropertyManagerImpl(DatabaseComponent db, TransportPropertyManagerImpl(DatabaseComponent db,
ClientHelper clientHelper, ClientHelper clientHelper,
IdentityManager identityManager,
ClientVersioningManager clientVersioningManager, ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser, MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory, Clock clock) { ContactGroupFactory contactGroupFactory, Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.identityManager = identityManager;
this.clientVersioningManager = clientVersioningManager; this.clientVersioningManager = clientVersioningManager;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
@@ -72,7 +68,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return; if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts // Set things up for any pre-existing contacts
@@ -82,7 +78,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public void addingContact(Transaction txn, Contact c) throws DbException { public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group to share with the contact // Create a group to share with the contact
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
db.addGroup(txn, g); db.addGroup(txn, g);
// Apply the client's visibility to the contact group // Apply the client's visibility to the contact group
Visibility client = clientVersioningManager.getClientVisibility(txn, Visibility client = clientVersioningManager.getClientVisibility(txn,
@@ -98,14 +94,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c, getLocalAuthorId(txn))); db.removeGroup(txn, getContactGroup(c));
} }
@Override @Override
public void onClientVisibilityChanging(Transaction txn, Contact c, public void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException { Visibility v) throws DbException {
// Apply the client's visibility to the contact group // Apply the client's visibility to the contact group
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
db.setGroupVisibility(txn, c.getId(), g.getId(), v); db.setGroupVisibility(txn, c.getId(), g.getId(), v);
} }
@@ -137,7 +133,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public void addRemoteProperties(Transaction txn, ContactId c, public void addRemoteProperties(Transaction txn, ContactId c,
Map<TransportId, TransportProperties> props) throws DbException { Map<TransportId, TransportProperties> props) throws DbException {
Group g = getContactGroup(db.getContact(txn, c), getLocalAuthorId(txn)); Group g = getContactGroup(db.getContact(txn, c));
for (Entry<TransportId, TransportProperties> e : props.entrySet()) { for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
storeMessage(txn, g.getId(), e.getKey(), e.getValue(), 0, storeMessage(txn, g.getId(), e.getKey(), e.getValue(), 0,
false, false); false, false);
@@ -196,16 +192,15 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
TransportId t) throws DbException { TransportId t) throws DbException {
return db.transactionWithResult(true, txn -> { return db.transactionWithResult(true, txn -> {
Map<ContactId, TransportProperties> remote = new HashMap<>(); Map<ContactId, TransportProperties> remote = new HashMap<>();
AuthorId local = getLocalAuthorId(txn);
for (Contact c : db.getContacts(txn)) for (Contact c : db.getContacts(txn))
remote.put(c.getId(), getRemoteProperties(txn, c, local, t)); remote.put(c.getId(), getRemoteProperties(txn, c, t));
return remote; return remote;
}); });
} }
private TransportProperties getRemoteProperties(Transaction txn, Contact c, private TransportProperties getRemoteProperties(Transaction txn, Contact c,
AuthorId local, TransportId t) throws DbException { TransportId t) throws DbException {
Group g = getContactGroup(c, local); Group g = getContactGroup(c);
try { try {
// Find the latest remote update // Find the latest remote update
LatestUpdate latest = findLatest(txn, g.getId(), t, false); LatestUpdate latest = findLatest(txn, g.getId(), t, false);
@@ -222,11 +217,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public TransportProperties getRemoteProperties(ContactId c, TransportId t) public TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException { throws DbException {
return db.transactionWithResult(true, txn -> { return db.transactionWithResult(true, txn ->
Contact contact = db.getContact(txn ,c); getRemoteProperties(txn, db.getContact(txn, c), t));
AuthorId local = getLocalAuthorId(txn);
return getRemoteProperties(txn, contact, local, t);
});
} }
@Override @Override
@@ -259,9 +251,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (latest != null) if (latest != null)
db.removeMessage(txn, latest.messageId); db.removeMessage(txn, latest.messageId);
// Store the merged properties in each contact's group // Store the merged properties in each contact's group
AuthorId localAuthorId = getLocalAuthorId(txn);
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
Group g = getContactGroup(c, localAuthorId); Group g = getContactGroup(c);
latest = findLatest(txn, g.getId(), t, true); latest = findLatest(txn, g.getId(), t, true);
version = latest == null ? 1 : latest.version + 1; version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, g.getId(), t, merged, version, storeMessage(txn, g.getId(), t, merged, version,
@@ -277,13 +268,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
} }
private AuthorId getLocalAuthorId(Transaction txn) throws DbException { private Group getContactGroup(Contact c) {
return identityManager.getLocalAuthor(txn).getId();
}
private Group getContactGroup(Contact c, AuthorId local) {
return contactGroupFactory.createContactGroup(CLIENT_ID, return contactGroupFactory.createContactGroup(CLIENT_ID,
MAJOR_VERSION, c, local); MAJOR_VERSION, c);
} }
private void storeMessage(Transaction txn, GroupId g, TransportId t, private void storeMessage(Transaction txn, GroupId g, TransportId t,

View File

@@ -12,12 +12,10 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility; import org.briarproject.bramble.api.sync.Group.Visibility;
@@ -55,12 +53,11 @@ import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@NotNullByDefault @NotNullByDefault
class ClientVersioningManagerImpl implements ClientVersioningManager, Client, class ClientVersioningManagerImpl implements ClientVersioningManager,
Service, ContactHook, IncomingMessageHook { Service, OpenDatabaseHook, ContactHook, IncomingMessageHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final IdentityManager identityManager;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
@@ -71,11 +68,9 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
@Inject @Inject
ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper, ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
IdentityManager identityManager,
ContactGroupFactory contactGroupFactory, Clock clock) { ContactGroupFactory contactGroupFactory, Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.identityManager = identityManager;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
this.clock = clock; this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
@@ -129,7 +124,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
} }
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return; if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts // Set things up for any pre-existing contacts
@@ -159,7 +154,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
@Override @Override
public void addingContact(Transaction txn, Contact c) throws DbException { public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group and share it with the contact // Create a group and share it with the contact
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
db.addGroup(txn, g); db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group // Attach the contact ID to the group
@@ -178,7 +173,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c, getLocalAuthorId(txn))); db.removeGroup(txn, getContactGroup(c));
} }
@Override @Override
@@ -313,7 +308,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
List<ClientVersion> versions) throws DbException { List<ClientVersion> versions) throws DbException {
try { try {
// Find the latest local and remote updates // Find the latest local and remote updates
Group g = getContactGroup(c, getLocalAuthorId(txn)); Group g = getContactGroup(c);
LatestUpdates latest = findLatestUpdates(txn, g.getId()); LatestUpdates latest = findLatestUpdates(txn, g.getId());
// Load and parse the latest local update // Load and parse the latest local update
if (latest.local == null) throw new DbException(); if (latest.local == null) throw new DbException();
@@ -349,20 +344,16 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
} }
} }
private AuthorId getLocalAuthorId(Transaction txn) throws DbException { private Group getContactGroup(Contact c) {
return identityManager.getLocalAuthor(txn).getId();
}
private Group getContactGroup(Contact c, AuthorId local) {
return contactGroupFactory.createContactGroup(CLIENT_ID, return contactGroupFactory.createContactGroup(CLIENT_ID,
MAJOR_VERSION, c, local); MAJOR_VERSION, c);
} }
@Nullable @Nullable
private LatestUpdates findLatestUpdates(Transaction txn, ContactId c) private LatestUpdates findLatestUpdates(Transaction txn, ContactId c)
throws DbException, FormatException { throws DbException, FormatException {
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
Group g = getContactGroup(contact, getLocalAuthorId(txn)); Group g = getContactGroup(contact);
// Contact may be in the process of being added or removed, so // Contact may be in the process of being added or removed, so
// contact group may not exist // contact group may not exist
if (!db.containsGroup(txn, g.getId())) return null; if (!db.containsGroup(txn, g.getId())) return null;

View File

@@ -34,7 +34,7 @@ public class VersioningModule {
ClientVersioningManagerImpl clientVersioningManager, ClientVersioningManagerImpl clientVersioningManager,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
ValidationManager validationManager) { ValidationManager validationManager) {
lifecycleManager.registerClient(clientVersioningManager); lifecycleManager.registerOpenDatabaseHook(clientVersioningManager);
lifecycleManager.registerService(clientVersioningManager); lifecycleManager.registerService(clientVersioningManager);
contactManager.registerContactHook(clientVersioningManager); contactManager.registerContactHook(clientVersioningManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.account;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
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.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
@@ -24,7 +25,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertNull;
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.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -47,7 +48,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
private final String encryptedKeyHex = toHexString(encryptedKey); private final String encryptedKeyHex = toHexString(encryptedKey);
private final byte[] newEncryptedKey = getRandomBytes(123); private final byte[] newEncryptedKey = getRandomBytes(123);
private final String newEncryptedKeyHex = toHexString(newEncryptedKey); private final String newEncryptedKeyHex = toHexString(newEncryptedKey);
private final LocalAuthor localAuthor = getLocalAuthor(); private final Identity identity = getIdentity();
private final LocalAuthor localAuthor = identity.getLocalAuthor();
private final String authorName = localAuthor.getName(); private final String authorName = localAuthor.getName();
private final String password = getRandomString(10); private final String password = getRandomString(10);
private final String newPassword = getRandomString(10); private final String newPassword = getRandomString(10);
@@ -251,9 +253,9 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testCreateAccountStoresDbKey() throws Exception { public void testCreateAccountStoresDbKey() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(identityManager).createLocalAuthor(authorName); oneOf(identityManager).createIdentity(authorName);
will(returnValue(localAuthor)); will(returnValue(identity));
oneOf(identityManager).registerLocalAuthor(localAuthor); oneOf(identityManager).registerIdentity(identity);
oneOf(crypto).generateSecretKey(); oneOf(crypto).generateSecretKey();
will(returnValue(key)); will(returnValue(key));
oneOf(crypto).encryptWithPassword(key.getBytes(), password); oneOf(crypto).encryptWithPassword(key.getBytes(), password);

View File

@@ -5,20 +5,25 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
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.AuthorInfo; import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.junit.Test; import org.junit.Test;
import java.util.Collection; import java.util.Collection;
import java.util.Random; import java.util.Random;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
@@ -28,6 +33,7 @@ import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -41,16 +47,19 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final KeyManager keyManager = context.mock(KeyManager.class); private final KeyManager keyManager = context.mock(KeyManager.class);
private final IdentityManager identityManager = private final IdentityManager identityManager =
context.mock(IdentityManager.class); context.mock(IdentityManager.class);
private final PendingContactFactory pendingContactFactory =
context.mock(PendingContactFactory.class);
private final ContactManager contactManager; private final ContactManager contactManager;
private final Author author = getAuthor(); private final Author remote = getAuthor();
private final LocalAuthor localAuthor = getLocalAuthor(); private final LocalAuthor localAuthor = getLocalAuthor();
private final AuthorId local = localAuthor.getId();
private final boolean verified = false, active = true; private final boolean verified = false, active = true;
private final Contact contact = getContact(author, verified); private final Contact contact = getContact(remote, local, verified);
private final ContactId contactId = contact.getId(); private final ContactId contactId = contact.getId();
public ContactManagerImplTest() { public ContactManagerImplTest() {
contactManager = contactManager = new ContactManagerImpl(db, keyManager,
new ContactManagerImpl(db, keyManager, identityManager); identityManager, pendingContactFactory);
} }
@Test @Test
@@ -62,7 +71,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(false), withDbCallable(txn)); oneOf(db).transactionWithResult(with(false), withDbCallable(txn));
oneOf(db).addContact(txn, author, verified); oneOf(db).addContact(txn, remote, local, verified);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(keyManager).addContact(txn, contactId, rootKey, timestamp, oneOf(keyManager).addContact(txn, contactId, rootKey, timestamp,
alice, active); alice, active);
@@ -70,12 +79,12 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
will(returnValue(contact)); will(returnValue(contact));
}}); }});
assertEquals(contactId, contactManager.addContact(author, rootKey, assertEquals(contactId, contactManager.addContact(remote, local,
timestamp, alice, verified, active)); rootKey, timestamp, alice, verified, active));
} }
@Test @Test
public void testGetContactByContactId() throws Exception { public void testGetContact() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
@@ -87,15 +96,41 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testGetContactByAuthorId() throws Exception { public void testGetContactByAuthor() throws Exception {
Transaction txn = new Transaction(null, true);
Collection<Contact> contacts = singletonList(contact);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contacts));
}});
assertEquals(contact, contactManager.getContact(remote.getId(), local));
}
@Test(expected = NoSuchContactException.class)
public void testGetContactByUnknownAuthor() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContact(txn, author.getId()); oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contact)); will(returnValue(emptyList()));
}}); }});
assertEquals(contact, contactManager.getContact(author.getId())); contactManager.getContact(remote.getId(), local);
}
@Test(expected = NoSuchContactException.class)
public void testGetContactByUnknownLocalAuthor() throws Exception {
Transaction txn = new Transaction(null, true);
Collection<Contact> contacts = singletonList(contact);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(contacts));
}});
contactManager.getContact(remote.getId(), new AuthorId(getRandomId()));
} }
@Test @Test
@@ -149,82 +184,78 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).containsContact(txn, author.getId()); oneOf(db).containsContact(txn, remote.getId(), local);
will(returnValue(true)); will(returnValue(true));
}}); }});
assertTrue(contactManager.contactExists(author.getId())); assertTrue(contactManager.contactExists(remote.getId(), local));
} }
@Test @Test
public void testGetAuthorInfoOurselves() throws Exception { public void testGetAuthorInfo() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn); oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor)); will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(singletonList(contact)));
}}); }});
AuthorInfo authorInfo = AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, localAuthor.getId()); contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
}
@Test
public void testGetAuthorInfoTransaction() throws DbException {
Transaction txn = new Transaction(null, true);
// check unknown author
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(emptyList()));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNKNOWN, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
// check unverified contact
checkAuthorInfoContext(txn, remote.getId(), singletonList(contact));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
// check verified contact
Contact verified = getContact(remote, local, true);
checkAuthorInfoContext(txn, remote.getId(), singletonList(verified));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(VERIFIED, authorInfo.getStatus());
assertEquals(verified.getAlias(), authorInfo.getAlias());
// check ourselves
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
never(db).getContactsByAuthorId(txn, remote.getId());
}});
authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId());
assertEquals(OURSELVES, authorInfo.getStatus()); assertEquals(OURSELVES, authorInfo.getStatus());
assertNull(authorInfo.getAlias()); assertNull(authorInfo.getAlias());
} }
@Test private void checkAuthorInfoContext(Transaction txn, AuthorId authorId,
public void testGetAuthorInfoVerified() throws Exception { Collection<Contact> contacts) throws DbException {
Transaction txn = new Transaction(null, true); context.checking(new Expectations() {{
Contact verified = getContact(author, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn); oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor)); will(returnValue(localAuthor));
oneOf(db).containsContact(txn, author.getId()); oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(true)); will(returnValue(contacts));
oneOf(db).getContact(txn, author.getId());
will(returnValue(verified));
}}); }});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, author.getId());
assertEquals(VERIFIED, authorInfo.getStatus());
assertEquals(verified.getAlias(), authorInfo.getAlias());
} }
@Test
public void testGetAuthorInfoUnverified() throws Exception {
Transaction txn = new Transaction(null, true);
Contact unverified = getContact(author, false);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).containsContact(txn, author.getId());
will(returnValue(true));
oneOf(db).getContact(txn, author.getId());
will(returnValue(unverified));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, author.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(unverified.getAlias(), authorInfo.getAlias());
}
@Test
public void testGetAuthorInfoUnknown() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).containsContact(txn, author.getId());
will(returnValue(false));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, author.getId());
assertEquals(UNKNOWN, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
}
} }

View File

@@ -0,0 +1,129 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.util.Base32;
import org.jmock.Expectations;
import org.junit.Test;
import java.security.GeneralSecurityException;
import static java.lang.System.arraycopy;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
public class PendingContactFactoryImplTest extends BrambleMockTestCase {
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final Clock clock = context.mock(Clock.class);
private final KeyParser keyParser = context.mock(KeyParser.class);
private final PublicKey publicKey = context.mock(PublicKey.class);
private final PendingContactFactory pendingContactFactory =
new PendingContactFactoryImpl(crypto, clock);
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final byte[] publicKeyBytes = getRandomBytes(RAW_LINK_BYTES - 1);
private final byte[] idBytes = getRandomId();
private final long timestamp = System.currentTimeMillis();
@Test(expected = FormatException.class)
public void testRejectsSyntacticallyInvalidLink() throws Exception {
pendingContactFactory.createPendingContact("briar://potato", alias);
}
@Test
public void testRejectsLinkWithUnknownFormatVersion() throws Exception {
String link = encodeLink(FORMAT_VERSION + 1);
try {
pendingContactFactory.createPendingContact(link, alias);
fail();
} catch (UnsupportedVersionException e) {
assertFalse(e.isTooOld());
}
}
@Test(expected = FormatException.class)
public void testRejectsLinkWithInvalidPublicKey() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes)));
will(throwException(new GeneralSecurityException()));
}});
pendingContactFactory.createPendingContact(encodeLink(), alias);
}
@Test
public void testAcceptsValidLinkWithoutPrefix() throws Exception {
testAcceptsValidLink(encodeLink());
}
@Test
public void testAcceptsValidLinkWithPrefix() throws Exception {
testAcceptsValidLink("briar://" + encodeLink());
}
@Test
public void testAcceptsValidLinkWithRubbish() throws Exception {
testAcceptsValidLink("before " + encodeLink() + " after");
}
@Test
public void testAcceptsValidLinkWithPrefixAndRubbish() throws Exception {
testAcceptsValidLink("before briar://" + encodeLink() + " after");
}
private void testAcceptsValidLink(String link) throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).getAgreementKeyParser();
will(returnValue(keyParser));
oneOf(keyParser).parsePublicKey(with(equal(publicKeyBytes)));
will(returnValue(publicKey));
allowing(publicKey).getEncoded();
will(returnValue(publicKeyBytes));
oneOf(crypto).hash(ID_LABEL, publicKeyBytes);
will(returnValue(idBytes));
oneOf(clock).currentTimeMillis();
will(returnValue(timestamp));
}});
PendingContact p =
pendingContactFactory.createPendingContact(link, alias);
assertArrayEquals(idBytes, p.getId().getBytes());
assertArrayEquals(publicKeyBytes, p.getPublicKey());
assertEquals(alias, p.getAlias());
assertEquals(WAITING_FOR_CONNECTION, p.getState());
assertEquals(timestamp, p.getTimestamp());
}
private String encodeLink() {
return encodeLink(FORMAT_VERSION);
}
private String encodeLink(int formatVersion) {
byte[] rawLink = new byte[RAW_LINK_BYTES];
rawLink[0] = (byte) formatVersion;
arraycopy(publicKeyBytes, 0, rawLink, 1, publicKeyBytes.length);
String base32 = Base32.encode(rawLink).toLowerCase();
assertEquals(BASE32_LINK_BYTES, base32.length());
return base32;
}
}

View File

@@ -11,16 +11,17 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchIdentityException;
import org.briarproject.bramble.api.db.NoSuchMessageException; import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchPendingContactException;
import org.briarproject.bramble.api.db.NoSuchTransportException; import org.briarproject.bramble.api.db.NoSuchTransportException;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.identity.event.LocalAuthorAddedEvent; import org.briarproject.bramble.api.identity.event.IdentityAddedEvent;
import org.briarproject.bramble.api.identity.event.LocalAuthorRemovedEvent; import org.briarproject.bramble.api.identity.event.IdentityRemovedEvent;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -65,6 +66,7 @@ import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -77,8 +79,9 @@ import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -104,6 +107,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final GroupId groupId; private final GroupId groupId;
private final Group group; private final Group group;
private final Author author; private final Author author;
private final Identity identity;
private final LocalAuthor localAuthor; private final LocalAuthor localAuthor;
private final String alias; private final String alias;
private final Message message, message1; private final Message message, message1;
@@ -122,7 +126,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
group = getGroup(clientId, majorVersion); group = getGroup(clientId, majorVersion);
groupId = group.getId(); groupId = group.getId();
author = getAuthor(); author = getAuthor();
localAuthor = getLocalAuthor(); identity = getIdentity();
localAuthor = identity.getLocalAuthor();
message = getMessage(groupId); message = getMessage(groupId);
message1 = getMessage(groupId); message1 = getMessage(groupId);
messageId = message.getId(); messageId = message.getId();
@@ -131,7 +136,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
metadata.put("foo", new byte[] {'b', 'a', 'r'}); metadata.put("foo", new byte[] {'b', 'a', 'r'});
transportId = getTransportId(); transportId = getTransportId();
maxLatency = Integer.MAX_VALUE; maxLatency = Integer.MAX_VALUE;
contact = getContact(author, true); contact = getContact(author, localAuthor.getId(), true);
contactId = contact.getId(); contactId = contact.getId();
alias = contact.getAlias(); alias = contact.getAlias();
keySetId = new TransportKeySetId(345); keySetId = new TransportKeySetId(345);
@@ -157,17 +162,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
// registerLocalAuthor() // addIdentity()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId()); oneOf(database).containsIdentity(txn, localAuthor.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor); oneOf(database).addIdentity(txn, identity);
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class))); oneOf(eventBus).broadcast(with(any(IdentityAddedEvent.class)));
// addContact() // addContact()
oneOf(database).containsLocalAuthor(txn, author.getId()); oneOf(database).containsIdentity(txn, localAuthor.getId());
will(returnValue(true));
oneOf(database).containsIdentity(txn, author.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(database).containsContact(txn, author.getId()); oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(database).addContact(txn, author, true); oneOf(database).addContact(txn, author, localAuthor.getId(), true);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class))); oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
// getContacts() // getContacts()
@@ -198,11 +206,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
oneOf(database).removeContact(txn, contactId); oneOf(database).removeContact(txn, contactId);
oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class))); oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class)));
// removeLocalAuthor() // removeIdentity()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId()); oneOf(database).containsIdentity(txn, localAuthor.getId());
will(returnValue(true)); will(returnValue(true));
oneOf(database).removeLocalAuthor(txn, localAuthor.getId()); oneOf(database).removeIdentity(txn, localAuthor.getId());
oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class))); oneOf(eventBus).broadcast(with(any(IdentityRemovedEvent.class)));
// endTransaction() // endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// close() // close()
@@ -213,8 +221,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
assertFalse(db.open(key, null)); assertFalse(db.open(key, null));
db.transaction(false, transaction -> { db.transaction(false, transaction -> {
db.addLocalAuthor(transaction, localAuthor); db.addIdentity(transaction, identity);
assertEquals(contactId, db.addContact(transaction, author, true)); assertEquals(contactId, db.addContact(transaction, author,
localAuthor.getId(), true));
assertEquals(singletonList(contact), assertEquals(singletonList(contact),
db.getContacts(transaction)); db.getContacts(transaction));
db.addGroup(transaction, group); // First time - listeners called db.addGroup(transaction, group); // First time - listeners called
@@ -223,7 +232,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.getGroups(transaction, clientId, majorVersion)); db.getGroups(transaction, clientId, majorVersion));
db.removeGroup(transaction, group); db.removeGroup(transaction, group);
db.removeContact(transaction, contactId); db.removeContact(transaction, contactId);
db.removeLocalAuthor(transaction, localAuthor.getId()); db.removeIdentity(transaction, localAuthor.getId());
}); });
db.close(); db.close();
} }
@@ -275,13 +284,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(18).of(database).startTransaction(); exactly(17).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(17).of(database).containsContact(txn, contactId); exactly(17).of(database).containsContact(txn, contactId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).containsContact(txn, author.getId()); exactly(17).of(database).abortTransaction(txn);
will(returnValue(false));
exactly(18).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
@@ -344,14 +351,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected // Expected
} }
try {
db.transaction(false, transaction ->
db.getContact(transaction, author.getId()));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.getMessageStatus(transaction, contactId, groupId)); db.getMessageStatus(transaction, contactId, groupId));
@@ -438,33 +437,52 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing() public void testVariousMethodsThrowExceptionIfIdentityIsMissing()
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Check whether the pseudonym is in the DB (which it's not) // Check whether the identity is in the DB (which it's not)
exactly(2).of(database).startTransaction(); exactly(4).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(2).of(database).containsLocalAuthor(txn, exactly(4).of(database).containsIdentity(txn, localAuthor.getId());
localAuthor.getId());
will(returnValue(false)); will(returnValue(false));
exactly(2).of(database).abortTransaction(txn); exactly(4).of(database).abortTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager); eventExecutor, shutdownManager);
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.getLocalAuthor(transaction, localAuthor.getId())); db.addContact(transaction, author, localAuthor.getId(),
true));
fail(); fail();
} catch (NoSuchLocalAuthorException expected) { } catch (NoSuchIdentityException expected) {
// Expected // Expected
} }
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.removeLocalAuthor(transaction, localAuthor.getId())); db.getIdentity(transaction, localAuthor.getId()));
fail(); fail();
} catch (NoSuchLocalAuthorException expected) { } catch (NoSuchIdentityException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.removeIdentity(transaction, localAuthor.getId()));
fail();
} catch (NoSuchIdentityException expected) {
// Expected
}
try {
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
byte[] privateKey = getRandomBytes(123);
db.transaction(false, transaction ->
db.setHandshakeKeyPair(transaction, localAuthor.getId(),
publicKey, privateKey));
fail();
} catch (NoSuchIdentityException expected) {
// Expected // Expected
} }
} }
@@ -1400,8 +1418,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsIdentity(txn, localAuthor.getId());
will(returnValue(true));
// Contact is a local identity // Contact is a local identity
oneOf(database).containsLocalAuthor(txn, author.getId()); oneOf(database).containsIdentity(txn, author.getId());
will(returnValue(true)); will(returnValue(true));
oneOf(database).abortTransaction(txn); oneOf(database).abortTransaction(txn);
}}); }});
@@ -1411,7 +1431,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addContact(transaction, author, true)); db.addContact(transaction, author, localAuthor.getId(),
true));
fail(); fail();
} catch (ContactExistsException expected) { } catch (ContactExistsException expected) {
// Expected // Expected
@@ -1423,10 +1444,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, author.getId()); oneOf(database).containsIdentity(txn, localAuthor.getId());
will(returnValue(true));
oneOf(database).containsIdentity(txn, author.getId());
will(returnValue(false)); will(returnValue(false));
// Contact already exists // Contact already exists for this local identity
oneOf(database).containsContact(txn, author.getId()); oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
will(returnValue(true)); will(returnValue(true));
oneOf(database).abortTransaction(txn); oneOf(database).abortTransaction(txn);
}}); }});
@@ -1436,7 +1460,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
try { try {
db.transaction(false, transaction -> db.transaction(false, transaction ->
db.addContact(transaction, author, true)); db.addContact(transaction, author, localAuthor.getId(),
true));
fail(); fail();
} catch (ContactExistsException expected) { } catch (ContactExistsException expected) {
// Expected // Expected
@@ -1444,7 +1469,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
@Test @Test
@SuppressWarnings("unchecked")
public void testMessageDependencies() throws Exception { public void testMessageDependencies() throws Exception {
int shutdownHandle = 12345; int shutdownHandle = 12345;
MessageId messageId2 = new MessageId(getRandomId()); MessageId messageId2 = new MessageId(getRandomId());

View File

@@ -4,6 +4,8 @@ import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
@@ -36,7 +38,7 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
@@ -130,10 +132,11 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
@Test @Test
public void testContainsContactByAuthorId() throws Exception { public void testContainsContactByAuthorId() throws Exception {
String name = "containsContact(T, AuthorId)"; String name = "containsContact(T, AuthorId, AuthorId)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.containsContact(txn, pickRandom(contacts).getAuthor().getId()); AuthorId remote = pickRandom(contacts).getAuthor().getId();
db.containsContact(txn, remote, localAuthor.getId());
db.commitTransaction(txn); db.commitTransaction(txn);
}); });
} }
@@ -159,11 +162,11 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
} }
@Test @Test
public void testContainsLocalAuthor() throws Exception { public void testContainsIdentity() throws Exception {
String name = "containsLocalAuthor(T, AuthorId)"; String name = "containsIdentity(T, AuthorId)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.containsLocalAuthor(txn, localAuthor.getId()); db.containsIdentity(txn, localAuthor.getId());
db.commitTransaction(txn); db.commitTransaction(txn);
}); });
} }
@@ -200,7 +203,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
} }
@Test @Test
public void testGetContactByContactId() throws Exception { public void testGetContact() throws Exception {
String name = "getContact(T, ContactId)"; String name = "getContact(T, ContactId)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -209,16 +212,6 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
}); });
} }
@Test
public void testGetContactByAuthorId() throws Exception {
String name = "getContact(T, AuthorId)";
benchmark(name, db -> {
Connection txn = db.startTransaction();
db.getContact(txn, pickRandom(contacts).getAuthor().getId());
db.commitTransaction(txn);
});
}
@Test @Test
public void testGetContacts() throws Exception { public void testGetContacts() throws Exception {
String name = "getContacts(T)"; String name = "getContacts(T)";
@@ -229,6 +222,27 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
}); });
} }
@Test
public void testGetContactsByRemoteAuthorId() throws Exception {
String name = "getContactsByAuthorId(T, AuthorId)";
benchmark(name, db -> {
Connection txn = db.startTransaction();
AuthorId remote = pickRandom(contacts).getAuthor().getId();
db.getContactsByAuthorId(txn, remote);
db.commitTransaction(txn);
});
}
@Test
public void testGetContactsByLocalAuthorId() throws Exception {
String name = "getContacts(T, AuthorId)";
benchmark(name, db -> {
Connection txn = db.startTransaction();
db.getContacts(txn, localAuthor.getId());
db.commitTransaction(txn);
});
}
@Test @Test
public void testGetGroup() throws Exception { public void testGetGroup() throws Exception {
String name = "getGroup(T, GroupId)"; String name = "getGroup(T, GroupId)";
@@ -282,21 +296,21 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
} }
@Test @Test
public void testGetLocalAuthor() throws Exception { public void testGetIdentity() throws Exception {
String name = "getLocalAuthor(T, AuthorId)"; String name = "getIdentity(T, AuthorId)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.getLocalAuthor(txn, localAuthor.getId()); db.getIdentity(txn, localAuthor.getId());
db.commitTransaction(txn); db.commitTransaction(txn);
}); });
} }
@Test @Test
public void testGetLocalAuthors() throws Exception { public void testGetIdentities() throws Exception {
String name = "getLocalAuthors(T)"; String name = "getIdentities(T)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.getLocalAuthors(txn); db.getIdentities(txn);
db.commitTransaction(txn); db.commitTransaction(txn);
}); });
} }
@@ -518,7 +532,8 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
} }
void populateDatabase(Database<Connection> db) throws DbException { void populateDatabase(Database<Connection> db) throws DbException {
localAuthor = getLocalAuthor(); Identity identity = getIdentity();
localAuthor = identity.getLocalAuthor();
clientIds = new ArrayList<>(); clientIds = new ArrayList<>();
contacts = new ArrayList<>(); contacts = new ArrayList<>();
groups = new ArrayList<>(); groups = new ArrayList<>();
@@ -530,9 +545,10 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
for (int i = 0; i < CLIENTS; i++) clientIds.add(getClientId()); for (int i = 0; i < CLIENTS; i++) clientIds.add(getClientId());
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.addLocalAuthor(txn, localAuthor); db.addIdentity(txn, identity);
for (int i = 0; i < CONTACTS; i++) { for (int i = 0; i < CONTACTS; i++) {
ContactId c = db.addContact(txn, getAuthor(), random.nextBoolean()); ContactId c = db.addContact(txn, getAuthor(), localAuthor.getId(),
random.nextBoolean());
contacts.add(db.getContact(txn, c)); contacts.add(db.getContact(txn, c));
contactGroups.put(c, new ArrayList<>()); contactGroups.put(c, new ArrayList<>());
for (int j = 0; j < GROUPS_PER_CONTACT; j++) { for (int j = 0; j < GROUPS_PER_CONTACT; j++) {

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MessageDeletedException; import org.briarproject.bramble.api.db.MessageDeletedException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -57,6 +58,7 @@ import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED; import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -73,9 +75,10 @@ import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getPendingContact; import static org.briarproject.bramble.test.TestUtils.getPendingContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
@@ -84,6 +87,7 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -102,6 +106,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private final int majorVersion; private final int majorVersion;
private final Group group; private final Group group;
private final Author author; private final Author author;
private final Identity identity;
private final LocalAuthor localAuthor; private final LocalAuthor localAuthor;
private final Message message; private final Message message;
private final MessageId messageId; private final MessageId messageId;
@@ -118,7 +123,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
group = getGroup(clientId, majorVersion); group = getGroup(clientId, majorVersion);
groupId = group.getId(); groupId = group.getId();
author = getAuthor(); author = getAuthor();
localAuthor = getLocalAuthor(); identity = getIdentity();
localAuthor = identity.getLocalAuthor();
message = getMessage(groupId); message = getMessage(groupId);
messageId = message.getId(); messageId = message.getId();
transportId = getTransportId(); transportId = getTransportId();
@@ -144,8 +150,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
db.addLocalAuthor(txn, localAuthor); db.addIdentity(txn, identity);
assertEquals(contactId, db.addContact(txn, author, true)); assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertFalse(db.containsGroup(txn, groupId)); assertFalse(db.containsGroup(txn, groupId));
db.addGroup(txn, group); db.addGroup(txn, group);
@@ -206,7 +213,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -235,7 +244,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared but unvalidated message // Add a contact, a shared group and a shared but unvalidated message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, UNKNOWN, true, null); db.addMessage(txn, message, UNKNOWN, true, null);
@@ -278,7 +289,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, an invisible group and a shared message // Add a contact, an invisible group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -327,7 +340,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and an unshared message // Add a contact, a shared group and an unshared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, false, null); db.addMessage(txn, message, DELIVERED, false, null);
@@ -356,14 +371,17 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
// The message is sendable, but too large to send // The message is sendable, but too large to send
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids =
message.getRawLength() - 1, MAX_LATENCY); db.getMessagesToSend(txn, contactId, message.getRawLength() - 1,
MAX_LATENCY);
assertTrue(ids.isEmpty()); assertTrue(ids.isEmpty());
// The message is just the right size to send // The message is just the right size to send
ids = db.getMessagesToSend(txn, contactId, message.getRawLength(), ids = db.getMessagesToSend(txn, contactId, message.getRawLength(),
@@ -380,7 +398,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact and a visible group // Add a contact and a visible group
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
@@ -419,7 +439,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -549,7 +571,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact and a shared group // Add a contact and a shared group
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
@@ -567,7 +591,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact // Add a contact
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
// The group is not in the database // The group is not in the database
assertFalse(db.containsVisibleMessage(txn, contactId, messageId)); assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
@@ -583,7 +609,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, an invisible group and a message // Add a contact, an invisible group and a message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -600,7 +628,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact and a group // Add a contact and a group
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
// The group should not be visible to the contact // The group should not be visible to the contact
@@ -650,7 +680,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
// Add the contact, the transport and the transport keys // Add the contact, the transport and the transport keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1)); assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
@@ -749,7 +781,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId)); assertEquals(emptyList(), db.getHandshakeKeys(txn, transportId));
// Add the contact, the transport and the handshake keys // Add the contact, the transport and the handshake keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId, assertEquals(handshakeKeySetId,
db.addHandshakeKeys(txn, contactId, keys)); db.addHandshakeKeys(txn, contactId, keys));
@@ -900,7 +934,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add the contact, transport and transport keys // Add the contact, transport and transport keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
@@ -942,7 +978,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add the contact, transport and handshake keys // Add the contact, transport and handshake keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId, assertEquals(handshakeKeySetId,
db.addHandshakeKeys(txn, contactId, keys)); db.addHandshakeKeys(txn, contactId, keys));
@@ -987,7 +1025,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add the contact, transport and transport keys // Add the contact, transport and transport keys
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys)); assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
@@ -1032,7 +1072,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add the contact, transport and handshake keys // Add the contact, transport and handshake keys
assertEquals(contactId, db.addContact(txn, author,true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
assertEquals(handshakeKeySetId, assertEquals(handshakeKeySetId,
db.addHandshakeKeys(txn, contactId, keys)); db.addHandshakeKeys(txn, contactId, keys));
@@ -1068,42 +1110,56 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
} }
@Test @Test
public void testGetContactByContactId() throws Exception { public void testGetContactsByAuthorId() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact // Add an identity for a local author - no contacts should be
assertEquals(contactId, db.addContact(txn, author, true)); // associated
db.addIdentity(txn, identity);
// Check the contact is returned // Add a contact associated with the local author
Contact c = db.getContact(txn, contactId); assertEquals(contactId,
assertEquals(contactId, c.getId()); db.addContact(txn, author, localAuthor.getId(), true));
assertEquals(author.getId(), c.getAuthor().getId());
assertEquals(author.getFormatVersion(), // Ensure contact is returned from database by author ID
c.getAuthor().getFormatVersion()); Collection<Contact> contacts =
assertEquals(author.getName(), c.getAuthor().getName()); db.getContactsByAuthorId(txn, author.getId());
assertArrayEquals(author.getPublicKey(), c.getAuthor().getPublicKey()); assertEquals(1, contacts.size());
assertEquals(contactId, contacts.iterator().next().getId());
// Ensure no contacts are returned after contact was deleted
db.removeContact(txn, contactId);
contacts = db.getContactsByAuthorId(txn, author.getId());
assertEquals(0, contacts.size());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@Test @Test
public void testGetContactByAuthorId() throws Exception { public void testGetContactsByLocalAuthorId() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact // Add an identity for a local author - no contacts should be
assertEquals(contactId, db.addContact(txn, author, true)); // associated
db.addIdentity(txn, identity);
Collection<ContactId> contacts =
db.getContacts(txn, localAuthor.getId());
assertEquals(emptyList(), contacts);
// Check the contact is returned // Add a contact associated with the local author
Contact c = db.getContact(txn, author.getId()); assertEquals(contactId,
assertEquals(contactId, c.getId()); db.addContact(txn, author, localAuthor.getId(), true));
assertEquals(author.getId(), c.getAuthor().getId()); contacts = db.getContacts(txn, localAuthor.getId());
assertEquals(author.getFormatVersion(), assertEquals(singletonList(contactId), contacts);
c.getAuthor().getFormatVersion());
assertEquals(author.getName(), c.getAuthor().getName()); // Remove the identity - the contact should be removed
assertArrayEquals(author.getPublicKey(), c.getAuthor().getPublicKey()); db.removeIdentity(txn, localAuthor.getId());
contacts = db.getContacts(txn, localAuthor.getId());
assertEquals(emptyList(), contacts);
assertFalse(db.containsContact(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -1115,7 +1171,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact - initially there should be no offered messages // Add a contact - initially there should be no offered messages
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
assertEquals(0, db.countOfferedMessages(txn, contactId)); assertEquals(0, db.countOfferedMessages(txn, contactId));
// Add some offered messages and count them // Add some offered messages and count them
@@ -1697,7 +1755,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -1794,13 +1854,44 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); db.close();
} }
@Test
public void testDifferentLocalAuthorsCanHaveTheSameContact()
throws Exception {
Identity identity1 = getIdentity();
LocalAuthor localAuthor1 = identity1.getLocalAuthor();
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add identities for two local authors
db.addIdentity(txn, identity);
db.addIdentity(txn, identity1);
// Add the same contact for each local author
ContactId contactId =
db.addContact(txn, author, localAuthor.getId(), true);
ContactId contactId1 =
db.addContact(txn, author, localAuthor1.getId(), true);
// The contacts should be distinct
assertNotEquals(contactId, contactId1);
assertEquals(2, db.getContacts(txn).size());
assertEquals(1, db.getContacts(txn, localAuthor.getId()).size());
assertEquals(1, db.getContacts(txn, localAuthor1.getId()).size());
db.commitTransaction(txn);
db.close();
}
@Test @Test
public void testDeleteMessage() throws Exception { public void testDeleteMessage() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -1852,7 +1943,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact // Add a contact
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
// The contact should have no alias // The contact should have no alias
Contact contact = db.getContact(txn, contactId); Contact contact = db.getContact(txn, contactId);
@@ -1907,7 +2000,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a group and a message // Add a contact, a group and a message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, UNKNOWN, false, null); db.addMessage(txn, message, UNKNOWN, false, null);
@@ -1989,7 +2084,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -2032,7 +2129,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
assertEquals(contactId, db.addContact(txn, author, true)); db.addIdentity(txn, identity);
assertEquals(contactId,
db.addContact(txn, author, localAuthor.getId(), true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true, null); db.addMessage(txn, message, DELIVERED, true, null);
@@ -2146,6 +2245,30 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); db.close();
} }
@Test
public void testSetHandshakeKeyPair() throws Exception {
Identity withoutKeys =
new Identity(localAuthor, null, null, identity.getTimeCreated());
assertFalse(withoutKeys.hasHandshakeKeyPair());
byte[] publicKey = getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES);
byte[] privateKey = getRandomBytes(123);
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
db.addIdentity(txn, withoutKeys);
Identity retrieved = db.getIdentity(txn, localAuthor.getId());
assertFalse(retrieved.hasHandshakeKeyPair());
db.setHandshakeKeyPair(txn, localAuthor.getId(), publicKey, privateKey);
retrieved = db.getIdentity(txn, localAuthor.getId());
assertTrue(retrieved.hasHandshakeKeyPair());
assertArrayEquals(publicKey, retrieved.getHandshakePublicKey());
assertArrayEquals(privateKey, retrieved.getHandshakePrivateKey());
db.commitTransaction(txn);
db.close();
}
private Database<Connection> open(boolean resume) throws Exception { private Database<Connection> open(boolean resume) throws Exception {
return open(resume, new TestMessageFactory(), new SystemClock()); return open(resume, new TestMessageFactory(), new SystemClock());
} }

View File

@@ -8,18 +8,17 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.AuthorFactory; import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.Collection; import static java.util.Collections.singletonList;
import java.util.Collections; import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
public class IdentityManagerImplTest extends BrambleMockTestCase { public class IdentityManagerImplTest extends BrambleMockTestCase {
@@ -28,67 +27,100 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final AuthorFactory authorFactory = private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class); context.mock(AuthorFactory.class);
private final PublicKey publicKey = context.mock(PublicKey.class); private final Clock clock = context.mock(Clock.class);
private final PrivateKey privateKey = context.mock(PrivateKey.class); private final PublicKey handshakePublicKey = context.mock(PublicKey.class);
private final PrivateKey handshakePrivateKey =
context.mock(PrivateKey.class);
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final LocalAuthor localAuthor = getLocalAuthor(); private final Identity identityWithKeys = getIdentity();
private final Collection<LocalAuthor> localAuthors = private final LocalAuthor localAuthor = identityWithKeys.getLocalAuthor();
Collections.singletonList(localAuthor); private final Identity identityWithoutKeys = new Identity(localAuthor,
private final String authorName = localAuthor.getName(); null, null, identityWithKeys.getTimeCreated());
private final KeyPair keyPair = new KeyPair(publicKey, privateKey); private final KeyPair handshakeKeyPair =
private final byte[] publicKeyBytes = localAuthor.getPublicKey(); new KeyPair(handshakePublicKey, handshakePrivateKey);
private final byte[] privateKeyBytes = localAuthor.getPrivateKey(); private final byte[] handshakePublicKeyBytes =
private IdentityManager identityManager; identityWithKeys.getHandshakePublicKey();
private final byte[] handshakePrivateKeyBytes =
identityWithKeys.getHandshakePrivateKey();
private IdentityManagerImpl identityManager;
@Before @Before
public void setUp() { public void setUp() {
identityManager = new IdentityManagerImpl(db, crypto, authorFactory); identityManager =
new IdentityManagerImpl(db, crypto, authorFactory, clock);
} }
@Test @Test
public void testCreateLocalAuthor() { public void testOpenDatabaseIdentityRegistered() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).generateSignatureKeyPair(); oneOf(db).addIdentity(txn, identityWithKeys);
will(returnValue(keyPair));
oneOf(publicKey).getEncoded();
will(returnValue(publicKeyBytes));
oneOf(privateKey).getEncoded();
will(returnValue(privateKeyBytes));
oneOf(authorFactory).createLocalAuthor(authorName,
publicKeyBytes, privateKeyBytes);
will(returnValue(localAuthor));
}}); }});
assertEquals(localAuthor, identityManager.registerIdentity(identityWithKeys);
identityManager.createLocalAuthor(authorName)); identityManager.onDatabaseOpened(txn);
} }
@Test @Test
public void testRegisterAndStoreLocalAuthor() throws Exception { public void testOpenDatabaseHandshakeKeysGenerated() throws Exception {
context.checking(new DbExpectations() {{ context.checking(new Expectations() {{
oneOf(db).transaction(with(false), withDbRunnable(txn)); oneOf(db).getIdentities(txn);
oneOf(db).addLocalAuthor(txn, localAuthor); will(returnValue(singletonList(identityWithoutKeys)));
oneOf(crypto).generateAgreementKeyPair();
will(returnValue(handshakeKeyPair));
oneOf(handshakePublicKey).getEncoded();
will(returnValue(handshakePublicKeyBytes));
oneOf(handshakePrivateKey).getEncoded();
will(returnValue(handshakePrivateKeyBytes));
oneOf(db).setHandshakeKeyPair(txn, localAuthor.getId(),
handshakePublicKeyBytes, handshakePrivateKeyBytes);
}}); }});
identityManager.registerLocalAuthor(localAuthor); identityManager.onDatabaseOpened(txn);
}
@Test
public void testOpenDatabaseNoHandshakeKeysGenerated() throws Exception {
context.checking(new Expectations() {{
oneOf(db).getIdentities(txn);
will(returnValue(singletonList(identityWithKeys)));
}});
identityManager.onDatabaseOpened(txn);
}
@Test
public void testGetLocalAuthorIdentityRegistered() throws DbException {
identityManager.registerIdentity(identityWithKeys);
assertEquals(localAuthor, identityManager.getLocalAuthor()); assertEquals(localAuthor, identityManager.getLocalAuthor());
identityManager.storeLocalAuthor();
} }
@Test @Test
public void testGetLocalAuthor() throws Exception { public void testGetLocalAuthorHandshakeKeysGenerated() throws Exception {
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getLocalAuthors(txn); oneOf(db).getIdentities(txn);
will(returnValue(localAuthors)); will(returnValue(singletonList(identityWithoutKeys)));
oneOf(crypto).generateAgreementKeyPair();
will(returnValue(handshakeKeyPair));
oneOf(handshakePublicKey).getEncoded();
will(returnValue(handshakePublicKeyBytes));
oneOf(handshakePrivateKey).getEncoded();
will(returnValue(handshakePrivateKeyBytes));
}}); }});
assertEquals(localAuthor, identityManager.getLocalAuthor()); assertEquals(localAuthor, identityManager.getLocalAuthor());
} }
@Test @Test
public void testGetCachedLocalAuthor() throws DbException { public void testGetLocalAuthorNoHandshakeKeysGenerated() throws Exception {
identityManager.registerLocalAuthor(localAuthor); context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(db).getIdentities(txn);
will(returnValue(singletonList(identityWithKeys)));
}});
assertEquals(localAuthor, identityManager.getLocalAuthor()); assertEquals(localAuthor, identityManager.getLocalAuthor());
} }

View File

@@ -2,11 +2,11 @@ package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;

View File

@@ -0,0 +1,53 @@
package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertEquals;
public class LifecycleManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final SecretKey dbKey = getSecretKey();
private LifecycleManagerImpl lifecycleManager;
@Before
public void setUp() {
lifecycleManager = new LifecycleManagerImpl(db, eventBus);
}
@Test
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {
Transaction txn = new Transaction(null, false);
AtomicBoolean called = new AtomicBoolean(false);
OpenDatabaseHook hook = transaction -> called.set(true);
context.checking(new DbExpectations() {{
oneOf(db).open(dbKey, lifecycleManager);
will(returnValue(false));
oneOf(db).transaction(with(false), withDbRunnable(txn));
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
}});
lifecycleManager.registerOpenDatabaseHook(hook);
assertEquals(SUCCESS, lifecycleManager.startServices(dbKey));
assertTrue(called.get());
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -14,18 +13,15 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.jmock.lib.concurrent.Synchroniser; import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Test; import org.junit.Test;
import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -37,16 +33,10 @@ public class PluginManagerImplTest extends BrambleTestCase {
setThreadingPolicy(new Synchroniser()); setThreadingPolicy(new Synchroniser());
}}; }};
Executor ioExecutor = Executors.newSingleThreadExecutor(); Executor ioExecutor = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
SecureRandom random = new SecureRandom();
Clock clock = context.mock(Clock.class);
EventBus eventBus = context.mock(EventBus.class); EventBus eventBus = context.mock(EventBus.class);
PluginConfig pluginConfig = context.mock(PluginConfig.class); PluginConfig pluginConfig = context.mock(PluginConfig.class);
ConnectionManager connectionManager = ConnectionManager connectionManager =
context.mock(ConnectionManager.class); context.mock(ConnectionManager.class);
ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
SettingsManager settingsManager = SettingsManager settingsManager =
context.mock(SettingsManager.class); context.mock(SettingsManager.class);
TransportPropertyManager transportPropertyManager = TransportPropertyManager transportPropertyManager =
@@ -122,9 +112,9 @@ public class PluginManagerImplTest extends BrambleTestCase {
oneOf(duplexPlugin).stop(); oneOf(duplexPlugin).stop();
}}); }});
PluginManagerImpl p = new PluginManagerImpl(ioExecutor, scheduler, PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
eventBus, pluginConfig, connectionManager, connectionRegistry, pluginConfig, connectionManager, settingsManager,
settingsManager, transportPropertyManager, random, clock); transportPropertyManager);
// Two plugins should be started and stopped // Two plugins should be started and stopped
p.startService(); p.startService();

View File

@@ -23,6 +23,7 @@ import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.RunAction; import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser; import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.security.SecureRandom; import java.security.SecureRandom;
@@ -39,7 +40,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
public class PollerTest extends BrambleMockTestCase { public class PollerImplTest extends BrambleMockTestCase {
private final ScheduledExecutorService scheduler = private final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class); context.mock(ScheduledExecutorService.class);
@@ -62,11 +63,20 @@ public class PollerTest extends BrambleMockTestCase {
private final int pollingInterval = 60 * 1000; private final int pollingInterval = 60 * 1000;
private final long now = System.currentTimeMillis(); private final long now = System.currentTimeMillis();
public PollerTest() { private PollerImpl poller;
public PollerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE); context.setImposteriser(ClassImposteriser.INSTANCE);
random = context.mock(SecureRandom.class); random = context.mock(SecureRandom.class);
} }
@Before
public void setUp() {
poller = new PollerImpl(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
}
@Test @Test
public void testConnectOnContactAdded() throws Exception { public void testConnectOnContactAdded() throws Exception {
// Two simplex plugins: one supports polling, the other doesn't // Two simplex plugins: one supports polling, the other doesn't
@@ -140,11 +150,7 @@ public class PollerTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new ContactAddedEvent(contactId));
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
p.eventOccurred(new ContactAddedEvent(contactId));
} }
@Test @Test
@@ -194,11 +200,7 @@ public class PollerTest extends BrambleMockTestCase {
transportId, duplexConnection); transportId, duplexConnection);
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
false)); false));
} }
@@ -225,11 +227,7 @@ public class PollerTest extends BrambleMockTestCase {
will(returnValue(future)); will(returnValue(future));
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false)); false));
} }
@@ -269,13 +267,9 @@ public class PollerTest extends BrambleMockTestCase {
will(returnValue(now + 1)); will(returnValue(now + 1));
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false)); false));
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId, poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false)); false));
} }
@@ -318,13 +312,9 @@ public class PollerTest extends BrambleMockTestCase {
with((long) pollingInterval - 2), with(MILLISECONDS)); with((long) pollingInterval - 2), with(MILLISECONDS));
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false)); false));
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId, poller.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false)); false));
} }
@@ -367,11 +357,7 @@ public class PollerTest extends BrambleMockTestCase {
oneOf(plugin).poll(singletonMap(contactId, properties)); oneOf(plugin).poll(singletonMap(contactId, properties));
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new TransportEnabledEvent(transportId));
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
p.eventOccurred(new TransportEnabledEvent(transportId));
} }
@Test @Test
@@ -412,11 +398,7 @@ public class PollerTest extends BrambleMockTestCase {
// All contacts are connected, so don't poll the plugin // All contacts are connected, so don't poll the plugin
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new TransportEnabledEvent(transportId));
connectionRegistry, pluginManager, transportPropertyManager,
random, clock);
p.eventOccurred(new TransportEnabledEvent(transportId));
} }
@Test @Test
@@ -442,11 +424,7 @@ public class PollerTest extends BrambleMockTestCase {
oneOf(future).cancel(false); oneOf(future).cancel(false);
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, poller.eventOccurred(new TransportEnabledEvent(transportId));
connectionRegistry, pluginManager, transportPropertyManager, poller.eventOccurred(new TransportDisabledEvent(transportId));
random, clock);
p.eventOccurred(new TransportEnabledEvent(transportId));
p.eventOccurred(new TransportDisabledEvent(transportId));
} }
} }

View File

@@ -11,8 +11,6 @@ import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
@@ -39,7 +37,6 @@ import static org.briarproject.bramble.api.properties.TransportPropertyManager.M
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -49,8 +46,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ClientVersioningManager clientVersioningManager = private final ClientVersioningManager clientVersioningManager =
context.mock(ClientVersioningManager.class); context.mock(ClientVersioningManager.class);
private final MetadataParser metadataParser = private final MetadataParser metadataParser =
@@ -60,7 +55,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
private final Clock clock = context.mock(Clock.class); private final Clock clock = context.mock(Clock.class);
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final LocalAuthor localAuthor = getLocalAuthor();
private final BdfDictionary fooPropertiesDict = BdfDictionary.of( private final BdfDictionary fooPropertiesDict = BdfDictionary.of(
new BdfEntry("fooKey1", "fooValue1"), new BdfEntry("fooKey1", "fooValue1"),
new BdfEntry("fooKey2", "fooValue2") new BdfEntry("fooKey2", "fooValue2")
@@ -87,8 +81,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
will(returnValue(localGroup)); will(returnValue(localGroup));
}}); }});
return new TransportPropertyManagerImpl(db, clientHelper, return new TransportPropertyManagerImpl(db, clientHelper,
identityManager, clientVersioningManager, metadataParser, clientVersioningManager, metadataParser, contactGroupFactory,
contactGroupFactory, clock); clock);
} }
@Test @Test
@@ -101,12 +95,10 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(db).containsGroup(txn, localGroup.getId()); oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(false)); will(returnValue(false));
oneOf(db).addGroup(txn, localGroup); oneOf(db).addGroup(txn, localGroup);
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).addGroup(txn, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(clientVersioningManager).getClientVisibility(txn, oneOf(clientVersioningManager).getClientVisibility(txn,
@@ -123,7 +115,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
1, true, true); 1, true, true);
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
t.createLocalState(txn); t.onDatabaseOpened(txn);
} }
@Test @Test
@@ -137,7 +129,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
}}); }});
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
t.createLocalState(txn); t.onDatabaseOpened(txn);
} }
@Test @Test
@@ -148,10 +140,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Create the group and share it with the contact // Create the group and share it with the contact
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).addGroup(txn, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(clientVersioningManager).getClientVisibility(txn, oneOf(clientVersioningManager).getClientVisibility(txn,
@@ -178,10 +168,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).removeGroup(txn, contactGroup); oneOf(db).removeGroup(txn, contactGroup);
}}); }});
@@ -319,10 +307,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).getContact(txn, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
}}); }});
expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict, expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
@@ -446,20 +432,18 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
context.checking(new DbExpectations() {{ context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(contacts)); will(returnValue(contacts));
// First contact: no updates // First contact: no updates
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact1, localAuthor.getId()); MAJOR_VERSION, contact1);
will(returnValue(contactGroup1)); will(returnValue(contactGroup1));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup1.getId()); contactGroup1.getId());
will(returnValue(Collections.emptyMap())); will(returnValue(Collections.emptyMap()));
// Second contact: returns an update // Second contact: returns an update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact2, localAuthor.getId()); MAJOR_VERSION, contact2);
will(returnValue(contactGroup2)); will(returnValue(contactGroup2));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup2.getId()); contactGroup2.getId());
@@ -526,12 +510,10 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
expectStoreMessage(txn, localGroup.getId(), "foo", expectStoreMessage(txn, localGroup.getId(), "foo",
fooPropertiesDict, 1, true, false); fooPropertiesDict, 1, true, false);
// Store the new properties in each contact's group, version 1 // Store the new properties in each contact's group, version 1
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId()); contactGroup.getId());
@@ -584,12 +566,10 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// Delete the previous update // Delete the previous update
oneOf(db).removeMessage(txn, localGroupUpdateId); oneOf(db).removeMessage(txn, localGroupUpdateId);
// Store the merged properties in each contact's group, version 2 // Store the merged properties in each contact's group, version 2
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId()); contactGroup.getId());

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@@ -32,7 +31,7 @@ public class TestLifecycleModule {
} }
@Override @Override
public void registerClient(Client c) { public void registerOpenDatabaseHook(OpenDatabaseHook hook) {
} }
@Override @Override

View File

@@ -0,0 +1,71 @@
package org.briarproject.bramble.util;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class Base32Test extends BrambleTestCase {
// Test vectors from RFC 4648
// https://tools.ietf.org/html/rfc4648#section-10
@Test
public void testEncoding() {
assertEquals("", Base32.encode(new byte[0]));
assertEquals("MY", Base32.encode(new byte[] {'f'}));
assertEquals("MZXQ", Base32.encode(new byte[] {'f', 'o'}));
assertEquals("MZXW6", Base32.encode(new byte[] {'f', 'o', 'o'}));
assertEquals("MZXW6YQ", Base32.encode(new byte[] {'f', 'o', 'o', 'b'}));
assertEquals("MZXW6YTB",
Base32.encode(new byte[] {'f', 'o', 'o', 'b', 'a'}));
assertEquals("MZXW6YTBOI",
Base32.encode(new byte[] {'f', 'o', 'o', 'b', 'a', 'r'}));
}
@Test
public void testStrictDecoding() {
testDecoding(true);
}
@Test
public void testNonStrictDecoding() {
testDecoding(false);
}
private void testDecoding(boolean strict) {
assertArrayEquals(new byte[0], Base32.decode("", strict));
assertArrayEquals(new byte[] {'f'}, Base32.decode("MY", strict));
assertArrayEquals(new byte[] {'f', 'o'}, Base32.decode("MZXQ", strict));
assertArrayEquals(new byte[] {'f', 'o', 'o'},
Base32.decode("MZXW6", strict));
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b'},
Base32.decode("MZXW6YQ", strict));
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b', 'a'},
Base32.decode("MZXW6YTB", strict));
assertArrayEquals(new byte[] {'f', 'o', 'o', 'b', 'a', 'r'},
Base32.decode("MZXW6YTBOI", strict));
}
@Test(expected = IllegalArgumentException.class)
public void testStrictDecodingRejectsNonZeroUnusedBits() {
Base32.decode("MZ", true);
}
@Test
public void testNonStrictDecodingAcceptsNonZeroUnusedBits() {
assertArrayEquals(new byte[] {'f'}, Base32.decode("MZ", false));
}
@Test
public void testRoundTrip() {
Random random = new Random();
byte[] data = new byte[100 + random.nextInt(100)];
random.nextBytes(data);
assertArrayEquals(data, Base32.decode(Base32.encode(data), true));
assertArrayEquals(data, Base32.decode(Base32.encode(data), false));
}
}

View File

@@ -10,8 +10,6 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility; import org.briarproject.bramble.api.sync.Group.Visibility;
@@ -38,7 +36,6 @@ import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MA
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
@@ -51,15 +48,12 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final ContactGroupFactory contactGroupFactory = private final ContactGroupFactory contactGroupFactory =
context.mock(ContactGroupFactory.class); context.mock(ContactGroupFactory.class);
private final Clock clock = context.mock(Clock.class); private final Clock clock = context.mock(Clock.class);
private final ClientVersioningHook hook = private final ClientVersioningHook hook =
context.mock(ClientVersioningHook.class); context.mock(ClientVersioningHook.class);
private final LocalAuthor localAuthor = getLocalAuthor();
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Contact contact = getContact(); private final Contact contact = getContact();
@@ -74,7 +68,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
will(returnValue(localGroup)); will(returnValue(localGroup));
}}); }});
return new ClientVersioningManagerImpl(db, clientHelper, return new ClientVersioningManagerImpl(db, clientHelper,
identityManager, contactGroupFactory, clock); contactGroupFactory, clock);
} }
@Test @Test
@@ -89,7 +83,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
expectAddingContact(); expectAddingContact();
ClientVersioningManagerImpl c = createInstance(); ClientVersioningManagerImpl c = createInstance();
c.createLocalState(txn); c.onDatabaseOpened(txn);
} }
@Test @Test
@@ -101,7 +95,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
}}); }});
ClientVersioningManagerImpl c = createInstance(); ClientVersioningManagerImpl c = createInstance();
c.createLocalState(txn); c.onDatabaseOpened(txn);
} }
@Test @Test
@@ -123,10 +117,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
new BdfEntry(MSG_KEY_LOCAL, true)); new BdfEntry(MSG_KEY_LOCAL, true));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).addGroup(txn, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
@@ -146,10 +138,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testRemovesGroupWhenRemovingContact() throws Exception { public void testRemovesGroupWhenRemovingContact() throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).removeGroup(txn, contactGroup); oneOf(db).removeGroup(txn, contactGroup);
}}); }});
@@ -184,12 +174,10 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addLocalMessage(txn, localVersions, new Metadata(), oneOf(db).addLocalMessage(txn, localVersions, new Metadata(),
false); false);
// Inform contacts that client versions have changed // Inform contacts that client versions have changed
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
// Find the latest local and remote updates (no remote update) // Find the latest local and remote updates (no remote update)
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
@@ -273,12 +261,10 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(), oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
false); false);
// Inform contacts that client versions have changed // Inform contacts that client versions have changed
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
// Find the latest local and remote updates (no remote update) // Find the latest local and remote updates (no remote update)
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
@@ -371,12 +357,10 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(), oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
false); false);
// Inform contacts that client versions have changed // Inform contacts that client versions have changed
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
// Find the latest local and remote updates // Find the latest local and remote updates
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
@@ -986,10 +970,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).getContact(txn, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact, localAuthor.getId()); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup.getId()); oneOf(db).containsGroup(txn, contactGroup.getId());
will(returnValue(exists)); will(returnValue(exists));

View File

@@ -2,15 +2,15 @@ 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:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed', 'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.dagger:dagger-compiler:2.19:dagger-compiler-2.19.jar:27a4b202a2de908182edb261f8c0a264e08e5e4733d7514bc7fbf0d31da5c0fc', 'com.google.dagger:dagger-compiler:2.22.1:dagger-compiler-2.22.1.jar:e5f28302cbe70a79d3620cddebfb8ec0736814f3980ffe1e673bfe3342f507d3',
'com.google.dagger:dagger-producers:2.19:dagger-producers-2.19.jar:a17663abe0fc38b676026950907d4c5f5e2bf338375415861eaff6e3bdb0b768', 'com.google.dagger:dagger-producers:2.22.1:dagger-producers-2.22.1.jar:f834a0082014213a68ff06a0f048d750178d02196c58b0b15beb367d32b97e35',
'com.google.dagger:dagger-spi:2.19:dagger-spi-2.19.jar:e7a6379d82c841f6aac2866948ad1eed716528707814602842a8d844ce04e2e1', 'com.google.dagger:dagger-spi:2.22.1:dagger-spi-2.22.1.jar:4b0b922793b3bcb91b99fabb75dba77c68afd7ae4c5f0c4fd6ba681f0a291c7d',
'com.google.dagger:dagger:2.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939', 'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8', 'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8',
'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:guava:25.0-jre:guava-25.0-jre.jar:3fd4341776428c7e0e5c18a7c10de129475b69ab9d30aeafbb5c277bb6074fa9',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f', 'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c', 'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728', 'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'com.squareup:javapoet:1.11.1:javapoet-1.11.1.jar:9cbf2107be499ec6e95afd36b58e3ca122a24166cdd375732e51267d64058e90', 'com.squareup:javapoet:1.11.1:javapoet-1.11.1.jar:9cbf2107be499ec6e95afd36b58e3ca122a24166cdd375732e51267d64058e90',

View File

@@ -19,7 +19,7 @@ dependencies {
tor 'org.briarproject:tor:0.3.5.8@zip' tor 'org.briarproject:tor:0.3.5.8@zip'
tor 'org.briarproject:obfs4proxy:0.0.7@zip' tor 'org.briarproject:obfs4proxy:0.0.7@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput')
@@ -28,7 +28,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.19' testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
} }
def torBinariesDir = 'src/main/resources' def torBinariesDir = 'src/main/resources'

View File

@@ -2,15 +2,15 @@ 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:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed', 'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.dagger:dagger-compiler:2.19:dagger-compiler-2.19.jar:27a4b202a2de908182edb261f8c0a264e08e5e4733d7514bc7fbf0d31da5c0fc', 'com.google.dagger:dagger-compiler:2.22.1:dagger-compiler-2.22.1.jar:e5f28302cbe70a79d3620cddebfb8ec0736814f3980ffe1e673bfe3342f507d3',
'com.google.dagger:dagger-producers:2.19:dagger-producers-2.19.jar:a17663abe0fc38b676026950907d4c5f5e2bf338375415861eaff6e3bdb0b768', 'com.google.dagger:dagger-producers:2.22.1:dagger-producers-2.22.1.jar:f834a0082014213a68ff06a0f048d750178d02196c58b0b15beb367d32b97e35',
'com.google.dagger:dagger-spi:2.19:dagger-spi-2.19.jar:e7a6379d82c841f6aac2866948ad1eed716528707814602842a8d844ce04e2e1', 'com.google.dagger:dagger-spi:2.22.1:dagger-spi-2.22.1.jar:4b0b922793b3bcb91b99fabb75dba77c68afd7ae4c5f0c4fd6ba681f0a291c7d',
'com.google.dagger:dagger:2.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939', 'com.google.dagger:dagger:2.22.1:dagger-2.22.1.jar:329d4340f24c4f5717af016c097e90668bfea2a5376e6aa9964b01cef3fd241a',
'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8', 'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8',
'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:guava:25.0-jre:guava-25.0-jre.jar:3fd4341776428c7e0e5c18a7c10de129475b69ab9d30aeafbb5c277bb6074fa9',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f', 'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.squareup:javapoet:1.11.1:javapoet-1.11.1.jar:9cbf2107be499ec6e95afd36b58e3ca122a24166cdd375732e51267d64058e90', 'com.squareup:javapoet:1.11.1:javapoet-1.11.1.jar:9cbf2107be499ec6e95afd36b58e3ca122a24166cdd375732e51267d64058e90',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg12"
sodipodi:docname="nearby.svg"
style="fill:none"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata18">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs16" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1020"
id="namedview14"
showgrid="false"
inkscape:zoom="15.170655"
inkscape:cx="9.6488139"
inkscape:cy="11.430963"
inkscape:window-x="1440"
inkscape:window-y="24"
inkscape:window-maximized="0"
inkscape:current-layer="svg12" />
<path
style="opacity:1;fill:#110000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.37658679px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 11.173062,2.4030645 C 9.9685672,2.4471125 8.7753453,2.7130268 7.6640583,3.2033016 8.187015,3.7262611 8.3967456,4.1446273 8.6059301,4.5629964 11.639055,3.412486 15.091396,4.0393524 17.497058,6.4449696 c 0.732151,0.7321412 1.359335,1.5691523 1.673062,2.5104781 0.418424,-0.2091845 0.941213,-0.3133673 1.464151,-0.3133672 h 0.104456 C 20.215788,7.3869735 19.482978,6.3401088 18.541616,5.2941863 16.528265,3.2807917 13.82295,2.3061587 11.173062,2.4030645 Z M 5.1695142,3.2316286 A 2.7193895,2.7193895 0 0 0 2.4501247,5.9510181 2.7193895,2.7193895 0 0 0 5.1695142,8.6704075 2.7193895,2.7193895 0 0 0 7.8889037,5.9510181 2.7193895,2.7193895 0 0 0 5.1695142,3.2316286 Z M 2.5404169,8.5358543 C 0.97153856,12.196552 1.7027262,16.484811 4.6313017,19.413413 c 1.8826514,1.987239 4.497171,2.930071 7.0073853,2.930071 2.510177,0 5.126431,-0.942832 7.009154,-2.930071 0.941272,-0.941362 1.672402,-2.091035 2.195341,-3.346124 h -0.104455 c -0.522939,0 -1.045818,-0.104155 -1.464151,-0.313367 -0.418423,0.941362 -0.940911,1.778328 -1.673062,2.510478 -3.242327,3.242419 -8.5770829,3.242419 -11.819429,0 C 3.27188,15.858828 2.6451458,12.406442 3.795656,9.3732707 3.377287,9.164086 2.9587814,8.9542233 2.5404169,8.5358543 Z m 9.0540081,0.8444984 a 2.9913288,2.9913284 0 0 0 -2.9902654,2.9902663 2.9913288,2.9913284 0 0 0 2.9902654,2.992036 2.9913288,2.9913284 0 0 0 2.992037,-2.992036 2.9913288,2.9913284 0 0 0 -2.992037,-2.9902663 z m 9.138991,0.423134 a 2.7193895,2.7193895 0 0 0 -2.71939,2.7193903 2.7193895,2.7193895 0 0 0 2.71939,2.719389 2.7193895,2.7193895 0 0 0 2.719389,-2.719389 2.7193895,2.7193895 0 0 0 -2.719389,-2.7193903 z"
id="path832-3-6"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="150"
height="178"
viewBox="0 0 150 178"
fill="none"
version="1.1"
id="svg129"
sodipodi:docname="nickname.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata133">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1020"
id="namedview131"
showgrid="false"
inkscape:zoom="3.7500494"
inkscape:cx="84.461975"
inkscape:cy="96.344913"
inkscape:window-x="1440"
inkscape:window-y="24"
inkscape:window-maximized="0"
inkscape:current-layer="svg129" />
<path
d="M 85.1905,0.127869 3.35693,0.328805 C 2.46521,0.330621 1.6107,0.690255 0.981217,1.32866 0.351735,1.96706 -0.0011965,2.83198 3.04782e-6,3.73331 L 0.372743,156.201 c 0.001185,0.446 0.089367,0.888 0.259505,1.3 0.170138,0.412 0.418902,0.786 0.732092,1.101 0.31319,0.315 0.68466,0.564 1.09322,0.734 0.40856,0.17 0.84619,0.257 1.28792,0.256 l 81.83362,-0.201 c 0.8917,-0.003 1.746,-0.363 2.375,-1.002 0.6291,-0.639 0.9814,-1.504 0.9796,-2.405 L 88.5542,3.51639 C 88.5512,2.61665 88.1955,1.75479 87.565,1.11965 86.9345,0.484503 86.0807,0.127864 85.1905,0.127869 Z"
id="path2"
inkscape:connector-curvature="0"
style="display:inline;opacity:0.25;fill:url(#paint0_linear)" />
<path
d="M 84.2815,-0.0019907 4.27572,0.195606 C 2.57395,0.199809 1.19777,1.59763 1.20193,3.31772 L 1.56691,154.298 c 0.00416,1.72 1.38709,3.111 3.08885,3.107 l 80.00584,-0.198 c 1.7017,-0.004 3.0779,-1.402 3.0737,-3.122 L 87.3704,3.1049 C 87.3662,1.38481 85.9833,-0.0061937 84.2815,-0.0019907 Z"
id="path4"
inkscape:connector-curvature="0"
style="display:inline;opacity:0.25;fill:#6d6d6d;fill-opacity:0.7" />
<path
style="display:inline;opacity:0.25;fill:#646464"
d="M 81.939453 3.9570312 L 65.933594 3.9960938 C 65.688694 5.6638138 64.861562 7.1881588 63.601562 8.2929688 C 62.341563 9.3977688 60.732453 10.010031 59.064453 10.019531 L 29.302734 10.091797 C 27.634734 10.090597 26.021259 9.4871919 24.755859 8.3886719 C 23.490459 7.2901519 22.655444 5.7700256 22.402344 4.1035156 L 6.6367188 4.1425781 C 5.7689587 4.1449981 4.9382819 4.4954875 4.3261719 5.1171875 C 3.7140519 5.7388975 3.3712469 6.5799313 3.3730469 7.4570312 L 3.4609375 44.068359 L 3.7167969 150.16797 C 3.7209769 151.04497 4.0691969 151.88495 4.6855469 152.50195 C 5.3018969 153.11895 6.1361462 153.46394 7.0039062 153.46094 L 82.306641 153.27539 C 83.171641 153.27039 83.999475 152.92078 84.609375 152.30078 C 85.219375 151.68078 85.560547 150.8408 85.560547 149.9668 L 85.306641 43.5625 L 85.220703 7.2558594 C 85.218303 6.3787594 84.870959 5.5386319 84.255859 4.9199219 C 83.640859 4.3012219 82.807253 3.9552112 81.939453 3.9570312 z M 52.652344 5.7910156 L 34.357422 6.0683594 C 34.107922 6.0721494 33.908409 6.27906 33.912109 6.53125 L 33.916016 6.8300781 C 33.919716 7.0822681 34.1255 7.28504 34.375 7.28125 L 52.671875 7.0019531 C 52.921375 6.9981731 53.118934 6.7912525 53.115234 6.5390625 L 53.111328 6.2402344 C 53.107628 5.9880444 52.901844 5.7872256 52.652344 5.7910156 z "
id="path6" />
<path
d="M 57.2036,6.934 C 57.6016,6.92797 57.9193,6.59699 57.9133,6.19476 57.9074,5.79252 57.5799,5.47134 57.182,5.47738 c -0.398,0.00604 -0.7157,0.33701 -0.7098,0.73924 0.006,0.40224 0.3335,0.72342 0.7314,0.71738 z"
id="path10"
inkscape:connector-curvature="0"
style="opacity:0.25;fill:#dbdbdb" />
<path
style="display:inline;opacity:0.25;fill:#e0e0e0"
d="m 17.640625,19.458984 c -4.1846,0 -7.576172,3.445513 -7.576172,7.695313 0,4.2498 3.391572,7.695312 7.576172,7.695312 4.1845,0 7.576172,-3.445512 7.576172,-7.695312 0,-4.2498 -3.391672,-7.695313 -7.576172,-7.695313 z m 19.882813,4.177735 v 3.234375 h 40.117187 v -3.234375 z m 0,6.466797 v 3.234375 H 77.640625 V 30.103516 Z M 17.640625,44.087891 c -4.1846,0 -7.576172,3.443559 -7.576172,7.693359 0,4.2498 3.391572,7.695313 7.576172,7.695312 4.1845,0 7.576172,-3.445512 7.576172,-7.695312 0,-4.2498 -3.391672,-7.693359 -7.576172,-7.693359 z m 19.882813,4.177734 V 51.5 h 40.117187 v -3.234375 z m 0,6.466797 v 3.232422 H 77.640625 V 54.732422 Z M 17.640625,68.714844 c -4.1846,0 -7.576172,3.445512 -7.576172,7.695312 0,4.2498 3.391572,7.695313 7.576172,7.695313 4.1845,0 7.576172,-3.445513 7.576172,-7.695313 0,-4.2498 -3.391672,-7.695312 -7.576172,-7.695312 z m 19.882813,4.179687 v 3.232422 h 40.117187 v -3.232422 z m 0,6.466797 V 82.59375 H 77.640625 V 79.361328 Z M 17.640625,93.34375 c -4.1846,0 -7.576172,3.445613 -7.576172,7.69531 0,4.25 3.391572,7.69532 7.576172,7.69532 4.1845,0 7.576172,-3.44532 7.576172,-7.69532 0,-4.249698 -3.391672,-7.69531 -7.576172,-7.69531 z m 19.882813,4.179688 v 3.232422 h 40.117187 v -3.232422 z m 0,6.464842 v 3.23438 h 40.117187 v -3.23438 z m -19.882813,13.98438 c -4.1846,0 -7.576172,3.44631 -7.576172,7.69531 0,4.25 3.391572,7.69336 7.576172,7.69336 4.1845,0 7.576172,-3.44336 7.576172,-7.69336 0,-4.249 -3.391672,-7.69531 -7.576172,-7.69531 z m 19.882813,4.17773 v 3.23438 h 40.117187 v -3.23438 z m 0,6.4668 v 3.23437 h 40.117187 v -3.23437 z"
id="path12"
inkscape:connector-curvature="0" />
<path
style="display:inline;fill:#313131"
inkscape:connector-curvature="0"
id="path44"
d="m 136.95,18.4066 -80.0056,0.1976 c -1.7018,0.0042 -3.078,1.402 -3.0738,3.1221 l 0.365,150.9807 c 0.0041,1.72 1.3871,3.111 3.0888,3.107 l 80.0056,-0.198 c 1.702,-0.004 3.078,-1.402 3.074,-3.122 L 140.039,21.5135 c -0.004,-1.7201 -1.387,-3.1111 -3.089,-3.1069 z" />
<path
style="display:inline;fill:#3e3e3e"
d="M 134.60938 22.357422 L 118.59961 22.394531 C 118.35561 24.062231 117.52758 25.586606 116.26758 26.691406 C 115.00758 27.796206 113.39847 28.408469 111.73047 28.417969 L 81.96875 28.492188 C 80.30065 28.490987 78.689228 27.885609 77.423828 26.787109 C 76.158428 25.688609 75.323313 24.168453 75.070312 22.501953 L 59.304688 22.541016 C 58.436887 22.543416 57.604288 22.893925 56.992188 23.515625 C 56.380088 24.137325 56.037262 24.980322 56.039062 25.857422 L 56.128906 62.46875 L 56.748047 62.466797 L 56.128906 62.470703 L 56.382812 168.57031 C 56.385213 169.44731 56.732656 170.28725 57.347656 170.90625 C 57.962756 171.52425 58.796362 171.87114 59.664062 171.86914 L 134.9668 171.68359 C 135.8348 171.68159 136.66534 171.33098 137.27734 170.70898 C 137.88934 170.08798 138.23247 169.24614 138.23047 168.36914 L 137.97266 62.21875 L 137.97461 62.21875 L 137.88867 25.65625 C 137.88667 24.77915 137.54078 23.939012 136.92578 23.320312 C 136.31078 22.701612 135.47738 22.355522 134.60938 22.357422 z "
id="path46" />
<path
style="display:inline;fill:#e0e0e0"
d="M 109.84961 23.890625 C 109.45161 23.896725 109.13462 24.228559 109.14062 24.630859 C 109.14663 25.033059 109.47309 25.353756 109.87109 25.347656 C 110.26909 25.341656 110.58608 25.009622 110.58008 24.607422 C 110.57408 24.205222 110.24761 23.884625 109.84961 23.890625 z M 105.32031 24.201172 L 87.025391 24.478516 C 86.775891 24.482316 86.576378 24.691159 86.580078 24.943359 L 86.583984 25.242188 C 86.587684 25.494387 86.793469 25.695206 87.042969 25.691406 L 105.33984 25.414062 C 105.58884 25.410363 105.7872 25.203372 105.7832 24.951172 L 105.7793 24.650391 C 105.7753 24.398191 105.56931 24.197372 105.32031 24.201172 z M 63.365234 37.568359 L 63.365234 53.738281 L 79.365234 53.738281 L 79.365234 37.568359 L 63.365234 37.568359 z M 86.748047 37.568359 L 86.748047 40.800781 L 126.86523 40.800781 L 126.86523 37.568359 L 86.748047 37.568359 z M 86.748047 44.037109 L 86.748047 47.269531 L 126.86523 47.269531 L 126.86523 44.037109 L 86.748047 44.037109 z M 71.787109 112.99609 C 67.602609 112.99609 64.210937 116.44141 64.210938 120.69141 C 64.210938 124.94141 67.602609 128.38672 71.787109 128.38672 C 75.971709 128.38672 79.365234 124.94141 79.365234 120.69141 C 79.365234 116.44141 75.971709 112.99609 71.787109 112.99609 z M 86.748047 116.92773 L 86.748047 120.16016 L 126.86523 120.16016 L 126.86523 116.92773 L 86.748047 116.92773 z M 86.748047 123.39258 L 86.748047 126.62695 L 126.86523 126.62695 L 126.86523 123.39258 L 86.748047 123.39258 z M 71.787109 142.60156 C 67.602609 142.60156 64.210937 146.04687 64.210938 150.29688 C 64.210938 154.54688 67.602609 157.99023 71.787109 157.99023 C 75.971709 157.99023 79.365234 154.54688 79.365234 150.29688 C 79.365234 146.04688 75.971709 142.60156 71.787109 142.60156 z M 86.748047 145.53516 L 86.748047 148.76758 L 126.86523 148.76758 L 126.86523 145.53516 L 86.748047 145.53516 z M 86.748047 152.00195 L 86.748047 155.23438 L 126.86523 155.23438 L 126.86523 152.00195 L 86.748047 152.00195 z "
id="path50" />
<path
style="display:inline;fill:#d5d6d7"
inkscape:connector-curvature="0"
id="path56"
d="M 148.744,68.6655 H 45.619 v 33.5835 h 103.125 z" />
<path
style="display:inline;fill:#87c214"
d="m 97.822266,50.503906 v 5.722656 h 17.966794 v -5.722656 z m -11.074219,30.59961 v 3.232422 h 40.117183 v -3.232422 z m 0,6.46875 v 3.232422 h 40.117183 v -3.232422 z"
id="path58"
inkscape:connector-curvature="0" />
<path
style="display:inline;fill:#f5f5f5"
inkscape:connector-curvature="0"
id="path68"
d="m 66.8652,92.3143 c 4.1846,0 7.5768,-3.4451 7.5768,-7.695 0,-4.2498 -3.3922,-7.6949 -7.5768,-7.6949 -4.1846,0 -7.5768,3.4451 -7.5768,7.6949 0,4.2499 3.3922,7.695 7.5768,7.695 z" />
<path
style="display:inline;fill:#87c214"
inkscape:connector-curvature="0"
id="path70"
d="m 70.1296,90.0058 h -6.5738 c -0.3153,0.0024 -0.6253,0.0818 -0.9038,0.2312 -0.2784,0.1495 -0.5171,0.3647 -0.6956,0.6274 1.4766,0.8563 3.1474,1.3114 4.8498,1.321 1.7023,0.0096 3.3781,-0.4266 4.864,-1.2662 -0.1666,-0.265 -0.3932,-0.486 -0.6612,-0.6448 -0.2679,-0.1588 -0.5693,-0.2509 -0.8794,-0.2686 z" />
<path
style="display:inline;fill:#333333"
d="m 66.861328,80.746094 c -2.0798,0 -3.765625,1.70444 -3.765625,3.80664 0,0.393506 0.07586,0.766 0.185547,1.123047 -0.327549,0.812667 -0.821888,1.878261 -1.158203,1.708985 0,0 5.069822,4.4252 9.544922,0 -0.355198,-0.613452 -0.773279,-1.18389 -1.21875,-1.732422 0.10502,-0.350063 0.177734,-0.714879 0.177734,-1.09961 0,-2.1022 -1.685825,-3.80664 -3.765625,-3.80664 z"
id="path72"
inkscape:connector-curvature="0" />
<path
style="display:inline;fill:#fda57d"
d="m 66.861328,81.570312 c -1.800651,0 -3.302121,1.279176 -3.673828,2.986329 -0.0049,-3.79e-4 -0.0088,-0.0059 -0.01367,-0.0059 -0.1933,0 -0.349609,0.297063 -0.349609,0.664063 0,0.347156 0.141531,0.622551 0.320312,0.652344 0.180942,1.407599 1.108928,2.5691 2.38086,3.058593 v 0.732422 c 0,0.3234 0.127315,0.634681 0.353515,0.863281 0.2263,0.2287 0.533616,0.357422 0.853516,0.357422 h 0.224609 c 0.1588,3e-4 0.316091,-0.03245 0.462891,-0.09375 0.1467,-0.0613 0.280278,-0.150172 0.392578,-0.263672 0.1123,-0.1135 0.201119,-0.248084 0.261719,-0.396484 0.0606,-0.1483 0.0921,-0.30825 0.0918,-0.46875 v -0.720703 c 1.288752,-0.483775 2.232282,-1.654559 2.412109,-3.076172 0.168226,-0.0485 0.298828,-0.311757 0.298828,-0.644531 0,-0.363137 -0.153161,-0.655938 -0.34375,-0.66211 -0.373249,-1.705035 -1.87271,-2.982422 -3.671875,-2.982422 z"
id="path78"
inkscape:connector-curvature="0" />
<path
style="display:inline;fill:#333333"
inkscape:connector-curvature="0"
id="path84"
d="m 63.2529,83.9344 h 7.1792 c 0,0 -0.6122,-2.9296 -3.3275,-2.7401 -2.7154,0.1895 -3.8517,2.7401 -3.8517,2.7401 z" />
<defs
id="defs127">
<linearGradient
id="paint0_linear"
x1="10.0511"
y1="162.566"
x2="80.1467"
y2="-2.31086"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#808080"
stop-opacity="0.25"
id="stop110" />
<stop
offset="0.54"
stop-color="#808080"
stop-opacity="0.12"
id="stop112" />
<stop
offset="1"
stop-color="#808080"
stop-opacity="0.1"
id="stop114" />
</linearGradient>
<linearGradient
id="paint1_linear"
x1="45488.3"
y1="16529.6"
x2="45488.3"
y2="10752.2"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#808080"
stop-opacity="0.25"
id="stop117" />
<stop
offset="0.54"
stop-color="#808080"
stop-opacity="0.12"
id="stop119" />
<stop
offset="1"
stop-color="#808080"
stop-opacity="0.1"
id="stop121" />
</linearGradient>
<clipPath
id="clip0">
<rect
width="150"
height="178"
fill="white"
id="rect124" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -118,14 +118,15 @@ dependencies {
implementation 'com.google.zxing:core:3.3.3' implementation 'com.google.zxing:core:3.3.3'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0' implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
implementation 'com.vanniktech:emoji-google:0.5.1' implementation 'com.vanniktech:emoji-google:0.5.1'
def glideVersion = '4.8.0' implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1' // later versions already use androidx
def glideVersion = '4.9.0'
implementation("com.github.bumptech.glide:glide:$glideVersion") { implementation("com.github.bumptech.glide:glide:$glideVersion") {
exclude group: 'com.android.support' exclude group: 'com.android.support'
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
} }
implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion" annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
@@ -134,7 +135,7 @@ dependencies {
testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'org.robolectric:robolectric:4.0.1' testImplementation 'org.robolectric:robolectric:4.0.1'
testImplementation 'org.robolectric:shadows-support-v4:3.3.2' testImplementation 'org.robolectric:shadows-support-v4:3.3.2'
testImplementation 'org.mockito:mockito-core:2.19.0' testImplementation 'org.mockito:mockito-core:2.25.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2"
@@ -144,7 +145,7 @@ dependencies {
androidTestImplementation "com.android.support.test.espresso:espresso-core:$espressoVersion" androidTestImplementation "com.android.support.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$espressoVersion" androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-intents:$espressoVersion" androidTestImplementation "com.android.support.test.espresso:espresso-intents:$espressoVersion"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.19" androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.22.1"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0' androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'junit:junit:4.12'
androidTestScreenshotImplementation "tools.fastlane:screengrab:1.2.0" androidTestScreenshotImplementation "tools.fastlane:screengrab:1.2.0"

View File

@@ -425,5 +425,31 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/BriarTheme.NoActionBar"/> android:theme="@style/BriarTheme.NoActionBar"/>
<activity
android:name=".android.contact.add.remote.AddContactActivity"
android:label="@string/add_contact_remotely_title_case"
android:launchMode="singleTask"
android:theme="@style/BriarTheme"
android:windowSoftInputMode="stateHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="briar"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
<activity
android:name=".android.contact.add.remote.PendingContactListActivity"
android:label="@string/pending_contact_requests"
android:theme="@style/BriarTheme"/>
</application> </application>
</manifest> </manifest>

View File

@@ -42,7 +42,7 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent; import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent; import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent; import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent; import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent;
import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent; import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent;
import java.util.Set; import java.util.Set;
@@ -99,7 +99,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private final Multiset<GroupId> groupCounts = new Multiset<>(); private final Multiset<GroupId> groupCounts = new Multiset<>();
private final Multiset<GroupId> forumCounts = new Multiset<>(); private final Multiset<GroupId> forumCounts = new Multiset<>();
private final Multiset<GroupId> blogCounts = new Multiset<>(); private final Multiset<GroupId> blogCounts = new Multiset<>();
private int introductionTotal = 0; private int contactAddedTotal = 0;
private int nextRequestId = 0; private int nextRequestId = 0;
private ContactId blockedContact = null; private ContactId blockedContact = null;
private GroupId blockedGroup = null; private GroupId blockedGroup = null;
@@ -171,7 +171,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
clearGroupMessageNotification(); clearGroupMessageNotification();
clearForumPostNotification(); clearForumPostNotification();
clearBlogPostNotification(); clearBlogPostNotification();
clearIntroductionSuccessNotification(); clearContactAddedNotification();
return null; return null;
}); });
try { try {
@@ -206,9 +206,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} }
@UiThread @UiThread
private void clearIntroductionSuccessNotification() { private void clearContactAddedNotification() {
introductionTotal = 0; contactAddedTotal = 0;
notificationManager.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID); notificationManager.cancel(CONTACT_ADDED_NOTIFICATION_ID);
} }
@Override @Override
@@ -230,8 +230,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} else if (e instanceof BlogPostAddedEvent) { } else if (e instanceof BlogPostAddedEvent) {
BlogPostAddedEvent b = (BlogPostAddedEvent) e; BlogPostAddedEvent b = (BlogPostAddedEvent) e;
if (!b.isLocal()) showBlogPostNotification(b.getGroupId()); if (!b.isLocal()) showBlogPostNotification(b.getGroupId());
} else if (e instanceof IntroductionSucceededEvent) { } else if (e instanceof ContactAddedRemotelyEvent) {
showIntroductionNotification(); showContactAddedNotification();
} }
} }
@@ -563,24 +563,24 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} }
@UiThread @UiThread
private void showIntroductionNotification() { private void showContactAddedNotification() {
introductionTotal++; contactAddedTotal++;
updateIntroductionNotification(); updateContactAddedNotification();
} }
@UiThread @UiThread
private void updateIntroductionNotification() { private void updateContactAddedNotification() {
BriarNotificationBuilder b = BriarNotificationBuilder b =
new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID); new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID);
b.setSmallIcon(R.drawable.notification_introduction); b.setSmallIcon(R.drawable.notification_contact_added);
b.setColorRes(R.color.briar_primary); b.setColorRes(R.color.briar_primary);
b.setContentTitle(appContext.getText(R.string.app_name)); b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString( b.setContentText(appContext.getResources().getQuantityString(
R.plurals.introduction_notification_text, introductionTotal, R.plurals.contact_added_notification_text, contactAddedTotal,
introductionTotal)); contactAddedTotal));
b.setNotificationCategory(CATEGORY_MESSAGE); b.setNotificationCategory(CATEGORY_MESSAGE);
setAlertProperties(b); setAlertProperties(b);
setDeleteIntent(b, INTRODUCTION_URI); setDeleteIntent(b, CONTACT_ADDED_URI);
// Touching the notification shows the contact list // Touching the notification shows the contact list
Intent i = new Intent(appContext, NavDrawerActivity.class); Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(INTENT_CONTACTS, true); i.putExtra(INTENT_CONTACTS, true);
@@ -591,14 +591,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
t.addNextIntent(i); t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0)); b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
notificationManager.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, notificationManager.notify(CONTACT_ADDED_NOTIFICATION_ID,
b.build()); b.build());
} }
@Override @Override
public void clearAllIntroductionNotifications() { public void clearAllContactAddedNotifications() {
androidExecutor.runOnUiThread( androidExecutor.runOnUiThread(this::clearContactAddedNotification);
this::clearIntroductionSuccessNotification);
} }
@Override @Override

View File

@@ -226,7 +226,7 @@ public class AppModule {
@Singleton @Singleton
RecentEmoji provideRecentEmoji(LifecycleManager lifecycleManager, RecentEmoji provideRecentEmoji(LifecycleManager lifecycleManager,
RecentEmojiImpl recentEmoji) { RecentEmojiImpl recentEmoji) {
lifecycleManager.registerClient(recentEmoji); lifecycleManager.registerOpenDatabaseHook(recentEmoji);
return recentEmoji; return recentEmoji;
} }
} }

View File

@@ -12,7 +12,7 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG
import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_URI; import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_URI;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_URI; import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_URI;
import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_URI; import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_URI;
import static org.briarproject.briar.api.android.AndroidNotificationManager.INTRODUCTION_URI; import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_ADDED_URI;
public class NotificationCleanupService extends IntentService { public class NotificationCleanupService extends IntentService {
@@ -46,8 +46,8 @@ public class NotificationCleanupService extends IntentService {
notificationManager.clearAllForumPostNotifications(); notificationManager.clearAllForumPostNotifications();
} else if (uri.equals(BLOG_URI)) { } else if (uri.equals(BLOG_URI)) {
notificationManager.clearAllBlogPostNotifications(); notificationManager.clearAllBlogPostNotifications();
} else if (uri.equals(INTRODUCTION_URI)) { } else if (uri.equals(CONTACT_ADDED_URI)) {
notificationManager.clearAllIntroductionNotifications(); notificationManager.clearAllContactAddedNotifications();
} }
} }
} }

View File

@@ -8,11 +8,11 @@ import com.vanniktech.emoji.emoji.Emoji;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
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.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -30,7 +30,7 @@ import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class RecentEmojiImpl implements RecentEmoji, Client { class RecentEmojiImpl implements RecentEmoji, OpenDatabaseHook {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(RecentEmojiImpl.class.getName()); Logger.getLogger(RecentEmojiImpl.class.getName());
@@ -72,7 +72,7 @@ class RecentEmojiImpl implements RecentEmoji, Client {
} }
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void onDatabaseOpened(Transaction txn) throws DbException {
Settings settings = Settings settings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE); settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
String serialized = settings.get(EMOJI_LRU_PREFERENCE); String serialized = settings.get(EMOJI_LRU_PREFERENCE);

View File

@@ -36,4 +36,9 @@ public interface TestingConstants {
*/ */
boolean FEATURE_FLAG_IMAGE_ATTACHMENTS = IS_DEBUG_BUILD; boolean FEATURE_FLAG_IMAGE_ATTACHMENTS = IS_DEBUG_BUILD;
/**
* Feature flag for enabling adding contacts at a distance.
*/
boolean FEATURE_FLAG_REMOTE_CONTACTS = IS_DEBUG_BUILD;
} }

View File

@@ -15,8 +15,12 @@ import org.briarproject.briar.android.blog.ReblogFragment;
import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.ContactListFragment; import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule; import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment; import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ImageActivity; import org.briarproject.briar.android.conversation.ImageActivity;
@@ -168,6 +172,10 @@ public interface ActivityComponent {
void inject(UnlockActivity activity); void inject(UnlockActivity activity);
void inject(AddContactActivity activity);
void inject(PendingContactListActivity activity);
// Fragments // Fragments
void inject(AuthorNameFragment fragment); void inject(AuthorNameFragment fragment);
@@ -191,6 +199,10 @@ public interface ActivityComponent {
void inject(KeyAgreementFragment fragment); void inject(KeyAgreementFragment fragment);
void inject(LinkExchangeFragment fragment);
void inject(NicknameFragment fragment);
void inject(ContactChooserFragment fragment); void inject(ContactChooserFragment fragment);
void inject(ShareForumFragment fragment); void inject(ShareForumFragment fragment);

View File

@@ -5,8 +5,6 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
@@ -34,6 +32,7 @@ import org.briarproject.briar.android.controller.handler.UiResultExceptionHandle
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity; import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
import org.briarproject.briar.android.sharing.ShareBlogActivity; import org.briarproject.briar.android.sharing.ShareBlogActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.blog.BlogPostHeader; import org.briarproject.briar.api.blog.BlogPostHeader;
@@ -44,6 +43,7 @@ import javax.inject.Inject;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@@ -356,17 +356,12 @@ public class BlogFragment extends BaseFragment
} }
private void displaySnackbar(int stringId, boolean scroll) { private void displaySnackbar(int stringId, boolean scroll) {
Snackbar snackbar = BriarSnackbarBuilder sb = new BriarSnackbarBuilder();
Snackbar.make(list, stringId, Snackbar.LENGTH_LONG);
snackbar.getView().setBackgroundResource(R.color.briar_primary);
if (scroll) { if (scroll) {
View.OnClickListener onClick = v -> list.smoothScrollToPosition(0); sb.setAction(R.string.blogs_blog_post_scroll_to,
snackbar.setActionTextColor(ContextCompat v -> list.smoothScrollToPosition(0));
.getColor(getContext(),
R.color.briar_button_text_positive));
snackbar.setAction(R.string.blogs_blog_post_scroll_to, onClick);
} }
snackbar.show(); sb.make(list, stringId, LENGTH_LONG).show();
} }
private void showDeleteDialog() { private void showDeleteDialog() {

View File

@@ -4,15 +4,12 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -23,6 +20,7 @@ import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.FeedController.FeedListener; import org.briarproject.briar.android.blog.FeedController.FeedListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogPostHeader; import org.briarproject.briar.api.blog.BlogPostHeader;
@@ -270,16 +268,12 @@ public class FeedFragment extends BaseFragment implements
int count = adapter.getItemCount(); int count = adapter.getItemCount();
boolean scroll = count > (lastVisible - firstVisible + 1); boolean scroll = count > (lastVisible - firstVisible + 1);
Snackbar s = Snackbar.make(list, stringRes, LENGTH_LONG); BriarSnackbarBuilder sb = new BriarSnackbarBuilder();
s.getView().setBackgroundResource(R.color.briar_primary);
if (scroll) { if (scroll) {
OnClickListener onClick = v -> list.smoothScrollToPosition(0); sb.setAction(R.string.blogs_blog_post_scroll_to,
s.setActionTextColor(ContextCompat v -> list.smoothScrollToPosition(0));
.getColor(getContext(),
R.color.briar_button_text_positive));
s.setAction(R.string.blogs_blog_post_scroll_to, onClick);
} }
s.show(); sb.make(list, stringRes, LENGTH_LONG).show();
} }
@Override @Override

View File

@@ -3,22 +3,25 @@ package org.briarproject.briar.android.contact;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair; import android.support.v4.util.Pair;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
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.event.Event; import org.briarproject.bramble.api.event.Event;
@@ -32,9 +35,12 @@ import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
@@ -49,20 +55,28 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static android.support.v4.view.ViewCompat.getTransitionName; import static android.support.v4.view.ViewCompat.getTransitionName;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_REMOTE_CONTACTS;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID; import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7; import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ContactListFragment extends BaseFragment implements EventListener { public class ContactListFragment extends BaseFragment implements EventListener,
OnMenuItemClickListener {
public static final String TAG = ContactListFragment.class.getName(); public static final String TAG = ContactListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG); private static final Logger LOG = Logger.getLogger(TAG);
@@ -76,6 +90,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
private ContactListAdapter adapter; private ContactListAdapter adapter;
private BriarRecyclerView list; private BriarRecyclerView list;
private Snackbar snackbar;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
@@ -107,7 +122,23 @@ public class ContactListFragment extends BaseFragment implements EventListener {
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.contact_list_button); requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
View contentView = inflater.inflate(R.layout.list, container, false); View contentView = inflater.inflate(R.layout.fragment_contact_list,
container, false);
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
if (FEATURE_FLAG_REMOTE_CONTACTS) {
speedDial.addOnMenuItemClickListener(this);
} else {
speedDial.setMenu(new FabSpeedDialMenu(contentView.getContext()));
speedDial.addOnStateChangeListener(open -> {
if (open) {
Intent intent = new Intent(getContext(),
ContactExchangeActivity.class);
startActivity(intent);
speedDial.closeMenu();
}
});
}
OnContactClickListener<ContactListItem> onContactClickListener = OnContactClickListener<ContactListItem> onContactClickListener =
(view, item) -> { (view, item) -> {
@@ -146,26 +177,28 @@ public class ContactListFragment extends BaseFragment implements EventListener {
list.setEmptyText(getString(R.string.no_contacts)); list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action)); list.setEmptyAction(getString(R.string.no_contacts_action));
snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, v ->
startActivity(new Intent(getContext(),
PendingContactListActivity.class)))
.make(contentView, R.string.pending_contact_requests_snackbar,
LENGTH_INDEFINITE);
return contentView; return contentView;
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
inflater.inflate(R.menu.contact_list_actions, menu); int itemId) {
super.onCreateOptionsMenu(menu, inflater); switch (itemId) {
} case R.id.action_add_contact_nearby:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_add_contact:
Intent intent = Intent intent =
new Intent(getContext(), ContactExchangeActivity.class); new Intent(getContext(), ContactExchangeActivity.class);
startActivity(intent); startActivity(intent);
return true; return;
default: case R.id.action_add_contact_remotely:
return super.onOptionsItemSelected(item); startActivity(
new Intent(getContext(), AddContactActivity.class));
} }
} }
@@ -174,11 +207,26 @@ public class ContactListFragment extends BaseFragment implements EventListener {
super.onStart(); super.onStart();
eventBus.addListener(this); eventBus.addListener(this);
notificationManager.clearAllContactNotifications(); notificationManager.clearAllContactNotifications();
notificationManager.clearAllIntroductionNotifications(); notificationManager.clearAllContactAddedNotifications();
loadContacts(); loadContacts();
checkForPendingContacts();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
private void checkForPendingContacts() {
listener.runOnDbThread(() -> {
try {
if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(() -> snackbar.dismiss());
} else {
runOnUiThreadUnlessDestroyed(() -> snackbar.show());
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
@@ -245,6 +293,16 @@ public class ContactListFragment extends BaseFragment implements EventListener {
(ConversationMessageReceivedEvent) e; (ConversationMessageReceivedEvent) e;
ConversationMessageHeader h = p.getMessageHeader(); ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h); updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactStateChangedEvent) {
PendingContactStateChangedEvent pe =
(PendingContactStateChangedEvent) e;
// only re-check pending contacts for initial state
if (pe.getPendingContactState() == WAITING_FOR_CONNECTION) {
checkForPendingContacts();
}
} else if (e instanceof PendingContactRemovedEvent ||
e instanceof ContactAddedRemotelyEvent) {
checkForPendingContacts();
} }
} }

View File

@@ -0,0 +1,105 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.ACTION_VIEW;
import static android.content.Intent.EXTRA_TEXT;
import static android.widget.Toast.LENGTH_LONG;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class AddContactActivity extends BriarActivity implements
BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddContactViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_fragment_container);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(AddContactViewModel.class);
viewModel.getRemoteLinkEntered().observeEvent(this, entered -> {
if (entered) {
NicknameFragment f = new NicknameFragment();
showNextFragment(f);
}
});
Intent i = getIntent();
if (i != null) {
onNewIntent(i);
setIntent(null); // don't keep the intent for configuration changes
}
if (state == null) {
showInitialFragment(new LinkExchangeFragment());
}
}
@Override
protected void onNewIntent(Intent i) {
super.onNewIntent(i);
String action = i.getAction();
if (ACTION_SEND.equals(action) || ACTION_VIEW.equals(action)) {
String text = i.getStringExtra(EXTRA_TEXT);
String uri = i.getDataString();
if (text != null) handleIncomingLink(text);
else if (uri != null) handleIncomingLink(uri);
}
}
private void handleIncomingLink(String link) {
if (link.equals(viewModel.getHandshakeLink().getValue())) {
Toast.makeText(this, R.string.intent_own_link, LENGTH_LONG)
.show();
} else if (viewModel.isValidRemoteContactLink(link)) {
viewModel.setRemoteHandshakeLink(link);
} else {
Toast.makeText(this, R.string.invalid_link, LENGTH_LONG)
.show();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,112 @@
package org.briarproject.briar.android.contact.add.remote;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class AddContactViewModel extends AndroidViewModel {
private final static Logger LOG =
getLogger(AddContactViewModel.class.getName());
private final ContactManager contactManager;
@DatabaseExecutor
private final Executor dbExecutor;
private final MutableLiveData<String> handshakeLink =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> remoteLinkEntered =
new MutableLiveEvent<>();
private final MutableLiveData<Boolean> addContactResult =
new MutableLiveData<>();
@Nullable
private String remoteHandshakeLink;
@Inject
public AddContactViewModel(@NonNull Application application,
ContactManager contactManager,
@DatabaseExecutor Executor dbExecutor) {
super(application);
this.contactManager = contactManager;
this.dbExecutor = dbExecutor;
loadHandshakeLink();
}
private void loadHandshakeLink() {
dbExecutor.execute(() -> {
try {
handshakeLink.postValue(contactManager.getHandshakeLink());
} catch (DbException e) {
logException(LOG, WARNING, e);
// the UI should stay disable in this case,
// leaving the user unable to proceed
}
});
}
LiveData<String> getHandshakeLink() {
return handshakeLink;
}
@Nullable
String getRemoteHandshakeLink() {
return remoteHandshakeLink;
}
void setRemoteHandshakeLink(String link) {
remoteHandshakeLink = link;
}
boolean isValidRemoteContactLink(@Nullable CharSequence link) {
return link != null && LINK_REGEX.matcher(link).find();
}
LiveEvent<Boolean> getRemoteLinkEntered() {
return remoteLinkEntered;
}
void onRemoteLinkEntered() {
if (remoteHandshakeLink == null) throw new IllegalStateException();
remoteLinkEntered.setEvent(true);
}
void addContact(String nickname) {
if (remoteHandshakeLink == null) throw new IllegalStateException();
dbExecutor.execute(() -> {
try {
contactManager.addPendingContact(remoteHandshakeLink, nickname);
addContactResult.postValue(true);
} catch (DbException | FormatException e) {
logException(LOG, WARNING, e);
addContactResult.postValue(false);
}
});
}
LiveData<Boolean> getAddContactResult() {
return addContactResult;
}
}

View File

@@ -0,0 +1,164 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.os.Bundle;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.ShareCompat.IntentBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import java.util.regex.Matcher;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Context.CLIPBOARD_SERVICE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class LinkExchangeFragment extends BaseFragment {
private static final String TAG = LinkExchangeFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddContactViewModel viewModel;
private ClipboardManager clipboard;
private TextInputLayout linkInputLayout;
private TextInputEditText linkInput;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
View v = inflater.inflate(R.layout.fragment_link_exchange,
container, false);
linkInputLayout = v.findViewById(R.id.linkInputLayout);
linkInput = v.findViewById(R.id.linkInput);
if (viewModel.getRemoteHandshakeLink() != null) {
// This can happen if the link was set via an incoming Intent
linkInput.setText(viewModel.getRemoteHandshakeLink());
}
clipboard = (ClipboardManager) requireNonNull(
getContext().getSystemService(CLIPBOARD_SERVICE));
Button pasteButton = v.findViewById(R.id.pasteButton);
pasteButton.setOnClickListener(view -> {
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null && clipData.getItemCount() > 0)
linkInput.setText(clipData.getItemAt(0).getText());
});
observeOnce(viewModel.getHandshakeLink(), this,
this::onHandshakeLinkLoaded);
return v;
}
private void onHandshakeLinkLoaded(String link) {
View v = requireNonNull(getView());
TextView linkView = v.findViewById(R.id.linkView);
linkView.setText(link);
Button copyButton = v.findViewById(R.id.copyButton);
ClipData clip = ClipData.newPlainText(
getString(R.string.link_clip_label), link);
copyButton.setOnClickListener(view -> {
clipboard.setPrimaryClip(clip);
Toast.makeText(getContext(), R.string.link_copied_toast,
LENGTH_SHORT).show();
});
copyButton.setEnabled(true);
Button shareButton = v.findViewById(R.id.shareButton);
shareButton.setOnClickListener(view ->
IntentBuilder.from(requireActivity())
.setText(link)
.setType("text/plain")
.startChooser());
shareButton.setEnabled(true);
Button continueButton = v.findViewById(R.id.addButton);
continueButton.setOnClickListener(view -> onContinueButtonClicked());
continueButton.setEnabled(true);
}
/**
* Requires {@link AddContactViewModel#getHandshakeLink()} to be loaded.
*/
@Nullable
private String getRemoteHandshakeLinkOrNull() {
CharSequence link = linkInput.getText();
if (link == null || link.length() == 0) {
linkInputLayout.setError(getString(R.string.missing_link));
linkInput.requestFocus();
return null;
}
Matcher matcher = LINK_REGEX.matcher(link);
if (matcher.find()) {
String linkWithoutSchema = matcher.group(2);
// Check also if this is our own link. This was loaded already,
// because it enables the Continue button which is the only caller.
if (("briar://" + linkWithoutSchema)
.equals(viewModel.getHandshakeLink().getValue())) {
linkInputLayout.setError(getString(R.string.own_link_error));
linkInput.requestFocus();
return null;
}
linkInputLayout.setError(null);
return linkWithoutSchema;
}
linkInputLayout.setError(getString(R.string.invalid_link));
linkInput.requestFocus();
return null;
}
private void onContinueButtonClicked() {
String link = getRemoteHandshakeLinkOrNull();
if (link == null) return; // invalid link
viewModel.setRemoteHandshakeLink(link);
viewModel.onRemoteLinkEntered();
}
}

View File

@@ -0,0 +1,121 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class NicknameFragment extends BaseFragment {
private static final String TAG = NicknameFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddContactViewModel viewModel;
private TextInputLayout contactNameLayout;
private TextInputEditText contactNameInput;
private Button addButton;
private ProgressBar progressBar;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
View v = inflater.inflate(R.layout.fragment_nickname,
container, false);
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
contactNameLayout = v.findViewById(R.id.contactNameLayout);
contactNameInput = v.findViewById(R.id.contactNameInput);
addButton = v.findViewById(R.id.addButton);
addButton.setOnClickListener(view -> onAddButtonClicked());
progressBar = v.findViewById(R.id.progressBar);
return v;
}
@Nullable
private String getNicknameOrNull() {
Editable name = contactNameInput.getText();
if (name == null || name.toString().trim().length() == 0) {
contactNameLayout.setError(getString(R.string.nickname_missing));
contactNameInput.requestFocus();
return null;
}
if (utf8IsTooLong(name.toString(), MAX_AUTHOR_NAME_LENGTH)) {
contactNameLayout.setError(getString(R.string.name_too_long));
contactNameInput.requestFocus();
return null;
}
contactNameLayout.setError(null);
return name.toString().trim();
}
private void onAddButtonClicked() {
String name = getNicknameOrNull();
if (name == null) return; // invalid nickname
addButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
viewModel.getAddContactResult().observe(this, success -> {
if (success == null) return;
if (success) {
Intent intent = new Intent(getActivity(),
PendingContactListActivity.class);
startActivity(intent);
} else {
Toast.makeText(getContext(), R.string.adding_contact_error,
LENGTH_LONG).show();
}
finish();
});
viewModel.addContact(name);
}
}

View File

@@ -0,0 +1,105 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.view.BriarRecyclerView;
import java.util.Collection;
import javax.annotation.Nullable;
import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class PendingContactListActivity extends BriarActivity
implements PendingContactListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private PendingContactListViewModel viewModel;
private PendingContactListAdapter adapter;
private BriarRecyclerView list;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.list);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(PendingContactListViewModel.class);
viewModel.getPendingContacts()
.observe(this, this::onPendingContactsChanged);
adapter = new PendingContactListAdapter(this, this, PendingContact.class);
list = findViewById(R.id.list);
list.setEmptyText(R.string.no_pending_contacts);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
list.showProgressBar();
}
@Override
public void onStart() {
super.onStart();
list.startPeriodicUpdate();
}
@Override
protected void onStop() {
super.onStop();
list.stopPeriodicUpdate();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onFailedPendingContactRemoved(PendingContact pendingContact) {
viewModel.removePendingContact(pendingContact.getId());
}
private void onPendingContactsChanged(Collection<PendingContact> contacts) {
if (contacts.isEmpty()) {
if (adapter.isEmpty()) {
list.showData(); // hides progress bar, shows empty text
} else {
// all previous contacts have been removed, so we can finish
supportFinishAfterTransition();
}
} else {
adapter.setItems(contacts);
}
}
}

View File

@@ -0,0 +1,59 @@
package org.briarproject.briar.android.contact.add.remote;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
@NotNullByDefault
class PendingContactListAdapter extends
BriarAdapter<PendingContact, PendingContactViewHolder> {
private final PendingContactListener listener;
PendingContactListAdapter(Context ctx, PendingContactListener listener,
Class<PendingContact> c) {
super(ctx, c);
this.listener = listener;
}
@Override
public PendingContactViewHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_pending_contact, viewGroup, false);
return new PendingContactViewHolder(v, listener);
}
@Override
public void onBindViewHolder(
PendingContactViewHolder pendingContactViewHolder, int i) {
pendingContactViewHolder.bind(items.get(i));
}
@Override
public int compare(PendingContact item1, PendingContact item2) {
return (int) (item1.getTimestamp() - item2.getTimestamp());
}
@Override
public boolean areContentsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getId().equals(item2.getId()) &&
item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp() &&
item1.getState() == item2.getState();
}
@Override
public boolean areItemsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getId().equals(item2.getId());
}
}

View File

@@ -0,0 +1,97 @@
package org.briarproject.briar.android.contact.add.remote;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedRemotelyEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class PendingContactListViewModel extends AndroidViewModel
implements EventListener {
private final Logger LOG =
getLogger(PendingContactListViewModel.class.getName());
@DatabaseExecutor
private final Executor dbExecutor;
private final ContactManager contactManager;
private final EventBus eventBus;
private final MutableLiveData<Collection<PendingContact>> pendingContacts =
new MutableLiveData<>();
@Inject
public PendingContactListViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
ContactManager contactManager, EventBus eventBus) {
super(application);
this.dbExecutor = dbExecutor;
this.contactManager = contactManager;
this.eventBus = eventBus;
this.eventBus.addListener(this);
loadPendingContacts();
}
@Override
protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedRemotelyEvent ||
e instanceof PendingContactStateChangedEvent ||
e instanceof PendingContactRemovedEvent) {
loadPendingContacts();
}
}
private void loadPendingContacts() {
dbExecutor.execute(() -> {
try {
pendingContacts.postValue(contactManager.getPendingContacts());
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
LiveData<Collection<PendingContact>> getPendingContacts() {
return pendingContacts;
}
void removePendingContact(PendingContactId id) {
dbExecutor.execute(() -> {
try {
contactManager.removePendingContact(id);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.briar.android.contact.add.remote;
import org.briarproject.bramble.api.contact.PendingContact;
interface PendingContactListener {
void onFailedPendingContactRemoved(PendingContact pendingContact);
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.briar.android.contact.add.remote;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.TextAvatarView;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
@NotNullByDefault
class PendingContactViewHolder extends ViewHolder {
private final PendingContactListener listener;
private final TextAvatarView avatar;
private final TextView name;
private final TextView time;
private final TextView status;
private final Button removeButton;
PendingContactViewHolder(View v, PendingContactListener listener) {
super(v);
avatar = v.findViewById(R.id.avatar);
name = v.findViewById(R.id.name);
time = v.findViewById(R.id.time);
status = v.findViewById(R.id.status);
removeButton = v.findViewById(R.id.removeButton);
this.listener = listener;
}
public void bind(PendingContact item) {
avatar.setText(item.getAlias());
avatar.setBackgroundBytes(item.getId().getBytes());
name.setText(item.getAlias());
time.setText(formatDate(time.getContext(), item.getTimestamp()));
removeButton.setOnClickListener(v -> {
listener.onFailedPendingContactRemoved(item);
removeButton.setEnabled(false);
});
int color = ContextCompat
.getColor(status.getContext(), R.color.briar_green);
int buttonVisibility = GONE;
switch (item.getState()) {
case WAITING_FOR_CONNECTION:
color = ContextCompat
.getColor(status.getContext(), R.color.briar_yellow);
status.setText(R.string.waiting_for_contact_to_come_online);
break;
case CONNECTED:
status.setText(R.string.connecting);
break;
case ADDING_CONTACT:
status.setText(R.string.adding_contact);
break;
case FAILED:
color = ContextCompat
.getColor(status.getContext(), R.color.briar_red);
status.setText(R.string.adding_contact_failed);
buttonVisibility = VISIBLE;
break;
default:
throw new IllegalStateException();
}
status.setTextColor(color);
removeButton.setVisibility(buttonVisibility);
removeButton.setEnabled(true);
}
}

View File

@@ -59,6 +59,7 @@ import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache
import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity; import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.view.ImagePreview; import org.briarproject.briar.android.view.ImagePreview;
import org.briarproject.briar.android.view.TextAttachmentController; import org.briarproject.briar.android.view.TextAttachmentController;
@@ -296,10 +297,10 @@ public class ConversationActivity extends BriarActivity
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) { if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
Snackbar snackbar = Snackbar.make(list, R.string.introduction_sent, new BriarSnackbarBuilder()
Snackbar.LENGTH_SHORT); .make(list, R.string.introduction_sent,
snackbar.getView().setBackgroundResource(R.color.briar_primary); Snackbar.LENGTH_SHORT)
snackbar.show(); .show();
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) { } else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS // remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
((TextAttachmentController) sendController).onImageReceived(data); ((TextAttachmentController) sendController).onImageReceived(data);
@@ -362,7 +363,7 @@ public class ConversationActivity extends BriarActivity
if (enable != null && enable) { if (enable != null && enable) {
menu.findItem(R.id.action_introduction).setEnabled(true); menu.findItem(R.id.action_introduction).setEnabled(true);
// show introduction onboarding, if needed // show introduction onboarding, if needed
observeOnce(viewModel.showIntroductionOnboarding(), this, viewModel.showIntroductionOnboarding().observeEvent(this,
this::showIntroductionOnboarding); this::showIntroductionOnboarding);
} }
}); });
@@ -473,9 +474,8 @@ public class ConversationActivity extends BriarActivity
if (revision == adapter.getRevision()) { if (revision == adapter.getRevision()) {
adapter.incrementRevision(); adapter.incrementRevision();
textInputView.setReady(true); textInputView.setReady(true);
// start observing onboarding after enabling (only once, because // start observing onboarding after enabling
// we only update this when an onboarding should be shown) viewModel.showImageOnboarding().observeEvent(this,
observeOnce(viewModel.showImageOnboarding(), this,
this::showImageOnboarding); this::showImageOnboarding);
List<ConversationItem> items = createItems(headers); List<ConversationItem> items = createItems(headers);
adapter.addAll(items); adapter.addAll(items);
@@ -498,7 +498,6 @@ public class ConversationActivity extends BriarActivity
* <p> * <p>
* Attention: Call this only after contactName has been initialized. * Attention: Call this only after contactName has been initialized.
*/ */
@SuppressWarnings("ConstantConditions")
private List<ConversationItem> createItems( private List<ConversationItem> createItems(
Collection<ConversationMessageHeader> headers) { Collection<ConversationMessageHeader> headers) {
List<ConversationItem> items = new ArrayList<>(headers.size()); List<ConversationItem> items = new ArrayList<>(headers.size());
@@ -714,8 +713,8 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void showImageOnboarding(@Nullable Boolean show) { private void showImageOnboarding(Boolean show) {
if (show == null || !show) return; if (!show) return;
if (SDK_INT >= 21) { if (SDK_INT >= 21) {
// show onboarding only after the enter transition has ended // show onboarding only after the enter transition has ended
// otherwise the tap target animation won't play // otherwise the tap target animation won't play

View File

@@ -28,6 +28,8 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
@@ -87,10 +89,10 @@ public class ConversationViewModel extends AndroidViewModel {
Transformations.map(contact, UiUtils::getContactDisplayName); Transformations.map(contact, UiUtils::getContactDisplayName);
private final MutableLiveData<Boolean> imageSupport = private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Boolean> showImageOnboarding = private final MutableLiveEvent<Boolean> showImageOnboarding =
new MutableLiveData<>(); new MutableLiveEvent<>();
private final MutableLiveData<Boolean> showIntroductionOnboarding = private final MutableLiveEvent<Boolean> showIntroductionOnboarding =
new MutableLiveData<>(); new MutableLiveEvent<>();
private final MutableLiveData<Boolean> showIntroductionAction = private final MutableLiveData<Boolean> showIntroductionAction =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Boolean> contactDeleted = private final MutableLiveData<Boolean> contactDeleted =
@@ -212,32 +214,30 @@ public class ConversationViewModel extends AndroidViewModel {
if (imagesSupported && if (imagesSupported &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) { settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
// check if we should show onboarding, only if images are supported // check if we should show onboarding, only if images are supported
showImageOnboarding.postValue(true); showImageOnboarding.postEvent(true);
// allow observer to stop listening for changes // allow observer to stop listening for changes
showIntroductionOnboarding.postValue(false); showIntroductionOnboarding.postEvent(false);
} else { } else {
// allow observer to stop listening for changes // allow observer to stop listening for changes
showImageOnboarding.postValue(false); showImageOnboarding.postEvent(false);
// we only show one onboarding dialog at a time // we only show one onboarding dialog at a time
if (introductionSupported && if (introductionSupported &&
settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, true)) { settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, true)) {
showIntroductionOnboarding.postValue(true); showIntroductionOnboarding.postEvent(true);
} else { } else {
// allow observer to stop listening for changes // allow observer to stop listening for changes
showIntroductionOnboarding.postValue(false); showIntroductionOnboarding.postEvent(false);
} }
} }
} }
@UiThread @UiThread
void onImageOnboardingSeen() { void onImageOnboardingSeen() {
showImageOnboarding.setValue(false);
onOnboardingSeen(SHOW_ONBOARDING_IMAGE); onOnboardingSeen(SHOW_ONBOARDING_IMAGE);
} }
@UiThread @UiThread
void onIntroductionOnboardingSeen() { void onIntroductionOnboardingSeen() {
showIntroductionOnboarding.setValue(false);
onOnboardingSeen(SHOW_ONBOARDING_INTRODUCTION); onOnboardingSeen(SHOW_ONBOARDING_INTRODUCTION);
} }
@@ -365,11 +365,11 @@ public class ConversationViewModel extends AndroidViewModel {
return imageSupport; return imageSupport;
} }
LiveData<Boolean> showImageOnboarding() { LiveEvent<Boolean> showImageOnboarding() {
return showImageOnboarding; return showImageOnboarding;
} }
LiveData<Boolean> showIntroductionOnboarding() { LiveEvent<Boolean> showIntroductionOnboarding() {
return showIntroductionOnboarding; return showIntroductionOnboarding;
} }

View File

@@ -9,7 +9,6 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.design.widget.AppBarLayout; import android.support.design.widget.AppBarLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.app.FragmentStatePagerAdapter;
@@ -32,6 +31,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.PullDownLayout; import org.briarproject.briar.android.view.PullDownLayout;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -100,7 +100,8 @@ public class ImageActivity extends BriarActivity
// get View Model // get View Model
viewModel = ViewModelProviders.of(this, viewModelFactory) viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ImageViewModel.class); .get(ImageViewModel.class);
viewModel.getSaveState().observe(this, this::onImageSaveStateChanged); viewModel.getSaveState().observeEvent(this,
this::onImageSaveStateChanged);
// inflate layout // inflate layout
setContentView(R.layout.activity_image); setContentView(R.layout.activity_image);
@@ -141,7 +142,8 @@ public class ImageActivity extends BriarActivity
viewPager.setCurrentItem(position); viewPager.setCurrentItem(position);
if (SDK_INT >= 16) { if (SDK_INT >= 16) {
viewModel.getOnImageClicked().observe(this, this::onImageClicked); viewModel.getOnImageClicked()
.observeEvent(this, this::onImageClicked);
window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT); window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT);
} }
} }
@@ -222,7 +224,6 @@ public class ImageActivity extends BriarActivity
private void onImageClicked(@Nullable Boolean clicked) { private void onImageClicked(@Nullable Boolean clicked) {
if (clicked != null && clicked) { if (clicked != null && clicked) {
toggleSystemUi(); toggleSystemUi();
viewModel.onOnImageClickSeen();
} }
} }
@@ -308,10 +309,10 @@ public class ImageActivity extends BriarActivity
R.string.save_image_error : R.string.save_image_success; R.string.save_image_error : R.string.save_image_success;
int colorRes = error ? int colorRes = error ?
R.color.briar_red : R.color.briar_primary; R.color.briar_red : R.color.briar_primary;
Snackbar s = Snackbar.make(layout, stringRes, LENGTH_LONG); new BriarSnackbarBuilder()
s.getView().setBackgroundResource(colorRes); .setBackgroundColor(colorRes)
s.show(); .make(layout, stringRes, LENGTH_LONG)
viewModel.onSaveStateSeen(); .show();
} }
AttachmentItem getVisibleAttachment() { AttachmentItem getVisibleAttachment() {

View File

@@ -2,8 +2,6 @@ package org.briarproject.briar.android.conversation;
import android.app.Application; import android.app.Application;
import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@@ -15,6 +13,8 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
@@ -53,9 +53,10 @@ public class ImageViewModel extends AndroidViewModel {
/** /**
* true means there was an error saving the image, false if image was saved. * true means there was an error saving the image, false if image was saved.
*/ */
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>(); private final MutableLiveEvent<Boolean> saveState =
private final MutableLiveData<Boolean> imageClicked = new MutableLiveEvent<>();
new MutableLiveData<>(); private final MutableLiveEvent<Boolean> imageClicked =
new MutableLiveEvent<>();
private int toolbarTop, toolbarBottom; private int toolbarTop, toolbarBottom;
@Inject @Inject
@@ -70,24 +71,17 @@ public class ImageViewModel extends AndroidViewModel {
} }
void clickImage() { void clickImage() {
imageClicked.setValue(true); imageClicked.setEvent(true);
} }
/** /**
* A LiveData that is true if the image was clicked, * A LiveEvent that is true if the image was clicked,
* false if it wasn't. * false if it wasn't.
*
* Call {@link #onOnImageClickSeen()} after consuming an update.
*/ */
LiveData<Boolean> getOnImageClicked() { LiveEvent<Boolean> getOnImageClicked() {
return imageClicked; return imageClicked;
} }
@UiThread
void onOnImageClickSeen() {
imageClicked.setValue(false);
}
void setToolbarPosition(int top, int bottom) { void setToolbarPosition(int top, int bottom) {
toolbarTop = top; toolbarTop = top;
toolbarBottom = bottom; toolbarBottom = bottom;
@@ -111,26 +105,18 @@ public class ImageViewModel extends AndroidViewModel {
/** /**
* A LiveData that is true if there was an error * A LiveData that is true if there was an error
* and false if the image was saved. * and false if the image was saved.
* It can be null otherwise, if no image was saved recently.
*
* Call {@link #onSaveStateSeen()} after consuming an update.
*/ */
LiveData<Boolean> getSaveState() { LiveEvent<Boolean> getSaveState() {
return saveState; return saveState;
} }
@UiThread
void onSaveStateSeen() {
saveState.setValue(null);
}
/** /**
* Saves the attachment to a writeable {@link Uri}. * Saves the attachment to a writeable {@link Uri}.
*/ */
@UiThread @UiThread
void saveImage(AttachmentItem attachment, @Nullable Uri uri) { void saveImage(AttachmentItem attachment, @Nullable Uri uri) {
if (uri == null) { if (uri == null) {
saveState.setValue(true); saveState.setEvent(true);
} else { } else {
saveImage(attachment, () -> getOutputStream(uri), null); saveImage(attachment, () -> getOutputStream(uri), null);
} }
@@ -155,7 +141,7 @@ public class ImageViewModel extends AndroidViewModel {
copyImageFromDb(a, osp, afterCopy); copyImageFromDb(a, osp, afterCopy);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
saveState.postValue(true); saveState.postEvent(true);
} }
}); });
} }
@@ -168,10 +154,10 @@ public class ImageViewModel extends AndroidViewModel {
OutputStream os = osp.getOutputStream(); OutputStream os = osp.getOutputStream();
copyAndClose(is, os); copyAndClose(is, os);
if (afterCopy != null) afterCopy.run(); if (afterCopy != null) afterCopy.run();
saveState.postValue(false); saveState.postEvent(false);
} catch (IOException e) { } catch (IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
saveState.postValue(true); saveState.postEvent(true);
} }
}); });
} }

View File

@@ -4,7 +4,6 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -27,6 +26,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseEventFragment; import org.briarproject.briar.android.fragment.BaseEventFragment;
import org.briarproject.briar.android.sharing.ForumInvitationActivity; import org.briarproject.briar.android.sharing.ForumInvitationActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
@@ -105,11 +105,9 @@ public class ForumListFragment extends BaseEventFragment implements
list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter); list.setAdapter(adapter);
snackbar = Snackbar.make(list, "", LENGTH_INDEFINITE); snackbar = new BriarSnackbarBuilder()
snackbar.getView().setBackgroundResource(R.color.briar_primary); .setAction(R.string.show, this)
snackbar.setAction(R.string.show, this); .make(list, "", LENGTH_INDEFINITE);
snackbar.setActionTextColor(ContextCompat
.getColor(getActivity(), R.color.briar_button_text_positive));
return contentView; return contentView;
} }

View File

@@ -15,6 +15,7 @@ import android.widget.Toast;
import com.google.zxing.Result; import com.google.zxing.Result;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
@@ -22,7 +23,6 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser; import org.briarproject.bramble.api.keyagreement.PayloadParser;
import org.briarproject.bramble.api.keyagreement.UnsupportedVersionException;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;

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