Compare commits

..

76 Commits

Author SHA1 Message Date
goapunk
a51f0f803f add bt polling speedtest 2018-05-10 14:42:09 +02:00
akwizgran
8f9d7a70bf Pause between connection attempts. 2018-05-08 14:15:39 +01:00
akwizgran
3ea642c6c0 Don't poll again if last poll is still running. 2018-05-08 13:51:39 +01:00
akwizgran
da0a32c613 Poll contacts in series rather than parallel. 2018-05-08 13:51:31 +01:00
akwizgran
46cd7e3719 Merge branch 'update_external_deps' into 'master'
Update external dependencies

See merge request akwizgran/briar!788
2018-05-08 12:34:44 +00:00
goapunk
496ffc22ba Update external dependencies
* Update CircleImageView
* Update Tap target prompt
2018-05-08 13:22:25 +02:00
akwizgran
2908145a06 Merge branch 'android-studio-3-1-debugging' into 'master'
Upgrade to Android Studio 3.1

See merge request akwizgran/briar!784
2018-05-07 14:16:24 +00:00
akwizgran
0d85a56a29 Merge branch '1216-fix-testdata' into 'master'
Resolve "IllegalArgumentException when creating test data with zero contacts"

Closes #1216

See merge request akwizgran/briar!785
2018-05-03 08:39:23 +00:00
goapunk
3e5d1a0d20 Fix testdata creation:
* Create at least one contact
2018-05-02 20:49:33 +02:00
akwizgran
0e9af1d993 Add new code style settings. 2018-05-02 17:13:27 +01:00
akwizgran
9b583ab7ae Build tools 26.0.2 no longer needed. 2018-05-02 16:14:40 +01:00
akwizgran
a10dc45a6a Remove old attempt to work around build issues. 2018-05-02 15:11:21 +01:00
akwizgran
3c6e2e8875 Upgrade support library and constraint layout. 2018-05-02 15:06:34 +01:00
akwizgran
cccdacc3e4 Upgrade to Android Studio 3.1. 2018-05-02 14:34:34 +01:00
akwizgran
1833efa51b Bump version numbers for 1.0.1 release. 2018-04-30 18:05:57 +01:00
akwizgran
77461619f0 Merge branch '1217-get-message-status' into 'master'
Return default message status if group is invisible

Closes #1217

See merge request akwizgran/briar!783
2018-04-30 16:23:19 +00:00
Torsten Grote
3bed6de93b Update translations 2018-04-30 12:52:46 -03:00
Torsten Grote
8e814e1bbf Merge branch '1215-disable-low-memory-shutdowns' into 'master'
Disable low memory shutdowns

See merge request akwizgran/briar!782
2018-04-30 13:43:17 +00:00
akwizgran
1b9f975199 Return default message status if group is invisible. 2018-04-30 13:55:40 +01:00
akwizgran
c1b8552c2b Merge branch 'introduction-test-fix' into 'master'
Fix introduction test non-determinism

See merge request akwizgran/briar!781
2018-04-30 08:05:00 +00:00
akwizgran
0091fcef5d Updated translations. 2018-04-30 07:46:00 +01:00
akwizgran
5d1a4acd15 Disable low memory shutdowns. 2018-04-30 07:43:16 +01:00
akwizgran
9be6d6c00f Bump version numbers for 1.0.0 release. 2018-04-29 20:28:53 +01:00
akwizgran
d5643d8e5d Merge branch '617-protocol-versioning-for-contact-exchange' into 'master'
Protocol versioning for the contact exchange protocol

Closes #617

See merge request akwizgran/briar!765
2018-04-29 16:40:05 +00:00
Torsten Grote
d9ed4a3ac9 Fix introduction test non-determinism 2018-04-29 13:33:59 -03:00
akwizgran
30bfa91fc4 Use new client helper methods for transport properties. 2018-04-29 17:27:46 +01:00
akwizgran
c3e4742bfe Use buffers for record headers. No need to buffer payloads. 2018-04-29 17:27:45 +01:00
akwizgran
d4b87983e8 Avoid an unnecessary copy when parsing messages. 2018-04-29 17:27:42 +01:00
akwizgran
eed1439745 Use generic record reader/writer for contact exchange. 2018-04-29 17:26:45 +01:00
akwizgran
4ba3708931 Use wildcards to allow easier construction. 2018-04-29 17:26:45 +01:00
akwizgran
215c62ed23 Use generic record reader/writer for sync. 2018-04-29 17:26:42 +01:00
akwizgran
4100daaa47 Rename sync record reader/writer. 2018-04-29 17:25:35 +01:00
akwizgran
6fa6ceb5ee Use generic record reader/writer for key agreement. 2018-04-29 17:25:34 +01:00
akwizgran
cc2791c37f Unit tests for generic record reader/writer. 2018-04-29 17:25:34 +01:00
akwizgran
20a131bec5 Add generic record reader and writer. 2018-04-29 17:25:34 +01:00
Torsten Grote
edee90dbe2 Merge branch '237-versioning-client' into 'master'
Client for negotiating supported clients

Closes #237

See merge request akwizgran/briar!759
2018-04-29 16:08:08 +00:00
akwizgran
fd78139a5a Remove stale comments. 2018-04-29 16:40:29 +01:00
akwizgran
41242ef369 Check that there's only one local client versions message. 2018-04-29 16:40:29 +01:00
akwizgran
c55bef95ce Fix rebasing mistakes. 2018-04-29 16:40:29 +01:00
akwizgran
fb6b487212 Unit tests for client versioning manager. 2018-04-29 16:40:28 +01:00
akwizgran
97f40bd20b Check whether hooks need to be called before loading contact. 2018-04-29 16:40:28 +01:00
akwizgran
36b191e9d4 Use remote states to update local states at startup. 2018-04-29 16:40:28 +01:00
akwizgran
ebaa50b101 Don't send redundant updates to new contacts. 2018-04-29 16:40:28 +01:00
akwizgran
4c5331888a Unit tests for client versioning validator. 2018-04-29 16:40:27 +01:00
akwizgran
c5efb6e16d Move versioning client to its own package. 2018-04-29 16:40:27 +01:00
akwizgran
522cba6ac3 Rename utility classes, add comment. 2018-04-29 16:40:27 +01:00
akwizgran
f25fbc5b94 Merge registration methods for clients and hooks. 2018-04-29 16:40:27 +01:00
akwizgran
57a6c8cb3a Separate the crypto executor into its own module.
This allows it to be replaced for testing.
2018-04-29 16:40:26 +01:00
akwizgran
6942a368d4 Don't share groups unless the contact supports the client. 2018-04-29 16:40:26 +01:00
akwizgran
c4e9b6f2ab Remove debug logging. 2018-04-29 16:40:26 +01:00
akwizgran
05deaf42e3 Store and exchange client minor versions.
These don't affect client visibility.
2018-04-29 16:40:26 +01:00
akwizgran
2e570ba50d Rename client version to major version. 2018-04-29 16:40:23 +01:00
akwizgran
cadb17987c Use client versioning for messaging. 2018-04-29 16:39:54 +01:00
akwizgran
e76f114a72 Use client versioning for introductions. 2018-04-29 16:39:54 +01:00
akwizgran
cb11b55a9a Use client versioning for transport properties. 2018-04-29 16:39:54 +01:00
akwizgran
f4c5855dd8 Use client versioning for private groups. 2018-04-29 16:39:53 +01:00
akwizgran
be309057cd Use client versioning for blogs and forums. 2018-04-29 16:39:53 +01:00
akwizgran
cf396c2ce2 Check whether contact group exists before using it. 2018-04-29 16:39:53 +01:00
akwizgran
a9f77f0f90 Add a method for getting a client's visibility. 2018-04-29 16:39:53 +01:00
akwizgran
cc6fed0298 Add javadocs. 2018-04-29 16:39:53 +01:00
akwizgran
66137d4cfa Add method for comparing visibilities. 2018-04-29 16:39:52 +01:00
akwizgran
114044ee5f Use client version to register validators, delivery hooks. 2018-04-29 16:39:52 +01:00
akwizgran
1197d65d8d Extract ClientVersion inner class. 2018-04-29 16:39:52 +01:00
akwizgran
85c11f8e1f Remove redundant checks when adding contacts.
Hooks are now called exactly once per contact.
2018-04-29 16:39:52 +01:00
akwizgran
8c00f2417b Add client version to groups table. 2018-04-29 16:39:51 +01:00
akwizgran
a38f39207f Initial implementation of client versioning client. 2018-04-29 16:39:51 +01:00
akwizgran
b7874365a3 Expose getMessageIds() through DatabaseComponent interface. 2018-04-29 16:39:51 +01:00
akwizgran
196caa7b45 Update ID of transport properties client. 2018-04-29 16:39:51 +01:00
akwizgran
3fd6ce2313 Fix javadoc. 2018-04-29 16:39:50 +01:00
akwizgran
c42852cde2 Merge branch '1213-update-transport-keys' into 'master'
Update transport keys in-place to retain key set IDs

Closes #1213

See merge request akwizgran/briar!779
2018-04-29 15:00:02 +00:00
Torsten Grote
a38b0a8527 Merge branch 'bluetooth-connection-limiter' into 'master'
Don't make or accept Bluetooth contact connections during key agreement

See merge request akwizgran/briar!770
2018-04-29 14:42:00 +00:00
Torsten Grote
79d6fd28de Merge branch '474-alice-flag' into 'master'
IntroduceeProtocolEngine uses wrong role when adding keys

See merge request akwizgran/briar!780
2018-04-29 02:27:18 +00:00
akwizgran
68132d893b IntroduceeProtocolEngine uses wrong role when adding keys. 2018-04-28 23:04:08 +01:00
akwizgran
6b011d2a7d Update transport keys in-place to retain key set IDs. 2018-04-28 22:15:59 +01:00
akwizgran
d7492df81c Skip UTestTest, which literally fails at random. 2018-04-28 14:52:56 +01:00
akwizgran
9efb6ab38f Don't allow BT contact connections during key agreement. 2018-04-24 10:45:23 +01:00
225 changed files with 6043 additions and 1744 deletions

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@ local.properties
.idea/* .idea/*
!.idea/runConfigurations/ !.idea/runConfigurations/
!.idea/codeStyleSettings.xml !.idea/codeStyleSettings.xml
!.idea/codeStyles
.gradle .gradle
build/ build/
*.iml *.iml

View File

@@ -8,8 +8,6 @@ cache:
before_script: before_script:
- set -e - set -e
- export GRADLE_USER_HOME=$PWD/.gradle - export GRADLE_USER_HOME=$PWD/.gradle
# Accept the license for the Android build tools
- echo y | /opt/android-sdk/tools/bin/sdkmanager "build-tools;26.0.2"
# Download OpenJDK 6 so we can compile against its standard library # Download OpenJDK 6 so we can compile against its standard library
- JDK_FILE=openjdk-6-jre-headless_6b38-1.13.10-1~deb7u1_amd64.deb - JDK_FILE=openjdk-6-jre-headless_6b38-1.13.10-1~deb7u1_amd64.deb
- if [ ! -d openjdk ] - if [ ! -d openjdk ]

261
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,261 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="100" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<JavaCodeStyleSettings>
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
</JavaCodeStyleSettings>
<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>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="Groovy">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="80" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_RESOURCES" value="false" />
<option name="ALIGN_MULTILINE_FOR" value="false" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="1" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_WRAP" value="1" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="ASSERT_STATEMENT_WRAP" value="1" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
<option name="ENUM_CONSTANTS_WRAP" value="1" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -9,13 +9,13 @@ apply plugin: 'de.undercouch.download'
android { android {
compileSdkVersion 27 compileSdkVersion 27
buildToolsVersion '26.0.2' buildToolsVersion '27.0.3'
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 1700 versionCode 10001
versionName "0.17.0" versionName "1.0.1"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }
@@ -36,23 +36,79 @@ dependencies {
dependencyVerification { dependencyVerification {
verify = [ verify = [
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.android.tools.analytics-library:protos:26.1.2:protos-26.1.2.jar:52672a0b42b572a06aecc3535d5068eb46c0e15d129b9f1085d3c16a1da5cdbb',
'com.android.tools.analytics-library:shared:26.1.2:shared-26.1.2.jar:5c7e0eda18c6f87feeb83628c707e8aaa3298b41fb72e38efe31ad1675f9e8e9',
'com.android.tools.analytics-library:tracker:26.1.2:tracker-26.1.2.jar:06f97aa0adf44ffb06f8681c6a79d9be153a08f61d21eddc42b8d3db96df4282',
'com.android.tools.build:apksig:3.1.2:apksig-3.1.2.jar:40696a4559124d1d57873d208857eee059d48859239d569c7d18374ac644a8be',
'com.android.tools.build:builder-model:3.1.2:builder-model-3.1.2.jar:d49bfa2a135c9562b6ca7aa4342036cfa1582c7074c2d1d93d1dae8b3a134e17',
'com.android.tools.build:builder-test-api:3.1.2:builder-test-api-3.1.2.jar:dfe2a50b740d41b11189101062434d4283d18647e89a492ad51710c719363e9f',
'com.android.tools.build:builder:3.1.2:builder-3.1.2.jar:b60f825a42e2efe8433619fbc759f3d9effecab718279048d36881188ceb1d14',
'com.android.tools.build:gradle-api:3.1.2:gradle-api-3.1.2.jar:e58bcc5b893e4583ab0f5c8ef89c4dbcce202b405a9d7fcc116d21e5357d4893',
'com.android.tools.build:manifest-merger:26.1.2:manifest-merger-26.1.2.jar:9c61c27ea5266573107b954acf1216d398f4d7e7ae6fad6409d6b2b767eb091c',
'com.android.tools.ddms:ddmlib:26.1.2:ddmlib-26.1.2.jar:18a2a5fbef36882f07d03c2b9e59eba05cf8248177bf5cbff736e4b582804c44',
'com.android.tools.external.com-intellij:intellij-core:26.1.2:intellij-core-26.1.2.jar:37c5acf279f1ae3e85b1a5be3c9f15f43bde7b08f978eefefffb9c4035760c52',
'com.android.tools.external.com-intellij:kotlin-compiler:26.1.2:kotlin-compiler-26.1.2.jar:152df0bee7580326c77316b669a9d96e3b09efb1d45f545dce4147271b0b8944',
'com.android.tools.external.org-jetbrains:uast:26.1.2:uast-26.1.2.jar:02d39582206d3f5fc0a6cb18bfd9e8b9f9c1acb805ec6dac08b4e3a56849d279',
'com.android.tools.layoutlib:layoutlib-api:26.1.2:layoutlib-api-26.1.2.jar:20220039fcc7d799f928153beff862e704457c0f55ab44258f3745ebeb662b4f',
'com.android.tools.lint:lint-api:26.1.2:lint-api-26.1.2.jar:e1d5b62b870a7c566e9877a6b96b27784a4d713f8caa07fdcb4705d47a40a1d9',
'com.android.tools.lint:lint-checks:26.1.2:lint-checks-26.1.2.jar:211e2afd58504372385d71b1e5be982c2b5121ab6fee1c04ddabeb75a8729e07',
'com.android.tools.lint:lint-gradle-api:26.1.2:lint-gradle-api-26.1.2.jar:71284f2a8b03c3e55c94511c9eb36f8184fbb85324325fc6b78abf5183f03d90',
'com.android.tools.lint:lint-gradle:26.1.2:lint-gradle-26.1.2.jar:855f0c82b7fc690df1b7319c0774f7517f7f8f5dd4eee1f6077dcf50e07c6240',
'com.android.tools.lint:lint-kotlin:26.1.2:lint-kotlin-26.1.2.jar:1e591f70bcbbc11569720a9bbcca2bc1f3d4f789f01f40f642848d920643d484',
'com.android.tools.lint:lint:26.1.2:lint-26.1.2.jar:93736c62e9f1976998c2b4aa716aea0734cdb162d05502f4af7292654aedb182',
'com.android.tools:annotations:26.1.2:annotations-26.1.2.jar:72773dcaf5c4ccca828e3c8467f1b78a8a00b3cc5f8ad1aab88fcf9379928018',
'com.android.tools:common:26.1.2:common-26.1.2.jar:ea4320f0c17dcbc4491896bb705c4d25ec08bd62ef02ab0579fe154e75e788e6',
'com.android.tools:dvlib:26.1.2:dvlib-26.1.2.jar:1187aa4fb666595c96c4deb6bc0e0f4b7e396bde9f6243330b49a232946130ea',
'com.android.tools:repository:26.1.2:repository-26.1.2.jar:8b86e512ad6d32bd76989451eefe2b271f5efce6d4d65ecb173afaf14606e01a',
'com.android.tools:sdk-common:26.1.2:sdk-common-26.1.2.jar:23584720a60a21cdcb5b1ec10269e3013789d6805d153cc696c39ec7ce251896',
'com.android.tools:sdklib:26.1.2:sdklib-26.1.2.jar:d3870fafc59ab8efa70d3f9649f40ee299c8ec5b58377b06e8853d7272a5bf4e',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.code.gson:gson:2.7:gson-2.7.jar:2d43eb5ea9e133d2ee2405cc14f5ee08951b8361302fdd93494a3a997b508d32',
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3', 'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b', 'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9', 'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.errorprone:error_prone_annotations:2.0.18:error_prone_annotations-2.0.18.jar:cb4cfad870bf563a07199f3ebea5763f0dec440fcda0b318640b1feaa788656b',
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99', 'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c', 'com.google.guava:guava:22.0:guava-22.0.jar:1158e94c7de4da480873f0b4ab4a1da14c0d23d4b1902cc94a58a6f0f9ab579e',
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728', 'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f',
'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.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4',
'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038',
'commons-codec:commons-codec:1.6:commons-codec-1.6.jar:54b34e941b8e1414bd3e40d736efd3481772dc26db3296f6aa45cec9f6203d86',
'commons-logging:commons-logging:1.1.1:commons-logging-1.1.1.jar:ce6f913cad1f0db3aad70186d65c5bc7ffcc9a99e3fe8e0b137312819f7c362f',
'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',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140', 'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', 'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5', 'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e', 'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.jacoco:org.jacoco.ant:0.7.4.201502262128:org.jacoco.ant-0.7.4.201502262128.jar:013ce2a68ba57a3c59215ae0dec4df3498c078062a38c3b94c841fc14450f283', 'org.apache.httpcomponents:httpclient:4.2.6:httpclient-4.2.6.jar:362e9324ee7c697e21279e20077b52737ddef3f1b2c1a7abe5ad34b465145550',
'org.jacoco:org.jacoco.core:0.7.4.201502262128:org.jacoco.core-0.7.4.201502262128.jar:ec4c74554312fac5116350164786f91b35c9e082fa4ea598bfa42b5db05d7abb', 'org.apache.httpcomponents:httpcore:4.2.5:httpcore-4.2.5.jar:e5e82da4cc66c8d917bbf743e3c0752efe8522735e7fc9dbddb65bccea81cfe9',
'org.jacoco:org.jacoco.report:0.7.4.201502262128:org.jacoco.report-0.7.4.201502262128.jar:7a3554c605e088e7e323b1084656243f0444fa353e2f2dee1f1a4204eb64ff09', 'org.apache.httpcomponents:httpmime:4.1:httpmime-4.1.jar:31629566148e8a47688ae43b420abc3ecd783ed15b33bebc00824bf24c9b15aa',
'org.ow2.asm:asm-debug-all:5.0.1:asm-debug-all-5.0.1.jar:4734de5b515a454b0096db6971fb068e5f70e6f10bbee2b3bd2fdfe5d978ed57', '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.codehaus.groovy:groovy-all:2.4.12:groovy-all-2.4.12.jar:6a56af4bd48903d56bec62821876cadefafd007360cc6bd0d8f7aa8d72b38be4',
'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d',
'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70',
'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea',
'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926',
'org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.0:kotlin-stdlib-jre8-1.2.0.jar:633524eee6ef1941f7cb1dab7ee3927b0a221ceee9047aeb5515f4cbb990c82a',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.0:kotlin-stdlib-1.2.0.jar:05cfd9f5ac0b41910703a8925f7211a495909b27a2ffdd1c5106f1689aeafcd4',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4',
'org.ow2.asm:asm-analysis:5.1:asm-analysis-5.1.jar:a34658f5c5de4b573eef21131cc32cc25f7b66407944f312b28ec2e56abb1fa9',
'org.ow2.asm:asm-commons:5.1:asm-commons-5.1.jar:97b3786e1f55e74bddf8ad102bf50e33bbcbc1f6b7fd7b36f0bbbb25cd4981be',
'org.ow2.asm:asm-tree:5.1:asm-tree-5.1.jar:c0de2bbc4cb8297419659813ecd4ed1d077ed1dd5c1f5544cc5143e493e84c10',
'org.ow2.asm:asm-util:5.1:asm-util-5.1.jar:ee032c39ae5e3cd099148fbba9a2124f9ed613e5cb93e03ee0fa8808ce364040',
'org.ow2.asm:asm:5.1:asm-5.1.jar:d2da399a9967c69f0a21739256fa79d284222c223082cacadc17372244764b54',
] ]
} }

View File

@@ -55,10 +55,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
// Non-null if the plugin started successfully // Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null; private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor, AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff, Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) { DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, callback, maxLatency); super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
maxLatency);
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
} }
@@ -154,7 +156,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
} }
private DuplexTransportConnection wrapSocket(BluetoothSocket s) { private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this, s); return new AndroidBluetoothTransportConnection(this,
connectionLimiter, s);
} }
@Override @Override

View File

@@ -59,11 +59,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor, AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
androidExecutor, appContext, secureRandom, backoff, callback, connectionLimiter, ioExecutor, androidExecutor, appContext,
MAX_LATENCY); secureRandom, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -14,10 +14,14 @@ import java.io.OutputStream;
class AndroidBluetoothTransportConnection class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection { extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager;
private final BluetoothSocket socket; private final BluetoothSocket socket;
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) { AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager,
BluetoothSocket socket) {
super(plugin); super(plugin);
this.connectionManager = connectionManager;
this.socket = socket; this.socket = socket;
} }
@@ -33,6 +37,10 @@ class AndroidBluetoothTransportConnection
@Override @Override
protected void closeConnection(boolean exception) throws IOException { protected void closeConnection(boolean exception) throws IOException {
socket.close(); try {
socket.close();
} finally {
connectionManager.connectionClosed(this);
}
} }
} }

View File

@@ -12,19 +12,19 @@ public interface ContactGroupFactory {
/** /**
* Creates a group that is not shared with any contacts. * Creates a group that is not shared with any contacts.
*/ */
Group createLocalGroup(ClientId clientId, int clientVersion); Group createLocalGroup(ClientId clientId, int majorVersion);
/** /**
* 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 clientVersion, Group createContactGroup(ClientId clientId, int majorVersion,
Contact contact); 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. * identified by their AuthorIds.
*/ */
Group createContactGroup(ClientId clientId, int clientVersion, Group createContactGroup(ClientId clientId, int majorVersion,
AuthorId authorId1, AuthorId authorId2); AuthorId authorId1, AuthorId authorId2);
} }

View File

@@ -13,9 +13,9 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
public interface ContactExchangeTask { public interface ContactExchangeTask {
/** /**
* The current version of the contact exchange protocol * The current version of the contact exchange protocol.
*/ */
int PROTOCOL_VERSION = 0; byte PROTOCOL_VERSION = 1;
/** /**
* Label for deriving Alice's header key from the master secret. * Label for deriving Alice's header key from the master secret.

View File

@@ -24,6 +24,8 @@ public interface ContactManager {
* Stores a contact associated with the given local and remote pseudonyms, * Stores a contact associated with the given local and remote pseudonyms,
* derives and stores transport keys for each transport, and returns an ID * derives and stores transport keys for each transport, and returns an ID
* for the contact. * for the contact.
*
* @param alice true if the local party is Alice
*/ */
ContactId addContact(Transaction txn, Author remote, AuthorId local, ContactId addContact(Transaction txn, Author remote, AuthorId local,
SecretKey master, long timestamp, boolean alice, boolean verified, SecretKey master, long timestamp, boolean alice, boolean verified,
@@ -38,7 +40,10 @@ public interface ContactManager {
/** /**
* Stores a contact associated with the given local and remote pseudonyms, * Stores a contact associated with the given local and remote pseudonyms,
* 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
*/ */
ContactId addContact(Author remote, AuthorId local, ContactId addContact(Author remote, AuthorId local,
SecretKey master, long timestamp, boolean alice, boolean verified, SecretKey master, long timestamp, boolean alice, boolean verified,

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.contact;
/**
* Record types for the contact exchange protocol.
*/
public interface RecordTypes {
byte CONTACT_INFO = 0;
}

View File

@@ -24,9 +24,9 @@ public class BdfDictionary extends TreeMap<String, Object> {
* ); * );
* </pre> * </pre>
*/ */
public static BdfDictionary of(Entry<String, Object>... entries) { public static BdfDictionary of(Entry<String, ?>... entries) {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
for (Entry<String, Object> e : entries) d.put(e.getKey(), e.getValue()); for (Entry<String, ?> e : entries) d.put(e.getKey(), e.getValue());
return d; return d;
} }

View File

@@ -136,8 +136,8 @@ public interface DatabaseComponent {
/** /**
* Deletes the message with the given ID. Unlike * Deletes the message with the given ID. Unlike
* {@link #removeMessage(Transaction, MessageId)}, the message ID and any * {@link #removeMessage(Transaction, MessageId)}, the message ID,
* other associated data are not deleted. * dependencies, metadata, and any other associated state are not deleted.
*/ */
void deleteMessage(Transaction txn, MessageId m) throws DbException; void deleteMessage(Transaction txn, MessageId m) throws DbException;
@@ -241,7 +241,8 @@ public interface DatabaseComponent {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException; Collection<Group> getGroups(Transaction txn, ClientId c, int majorVersion)
throws DbException;
/** /**
* Returns the given group's visibility to the given contact, or * Returns the given group's visibility to the given contact, or
@@ -266,6 +267,14 @@ public interface DatabaseComponent {
*/ */
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException; Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/**
* Returns the IDs of all delivered messages in the given group.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated.
* <p/> * <p/>
@@ -310,9 +319,9 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Returns the metadata for any messages in the given group with metadata * Returns the metadata for any delivered messages in the given group with
* that matches all entries in the given query. If the query is empty, the * metadata that matches all entries in the given query. If the query is
* metadata for all messages is returned. * empty, the metadata for all delivered messages is returned.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -328,8 +337,8 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Returns the metadata for the given delivered and pending message. * Returns the metadata for the given delivered or pending message.
* This is meant to be only used by the ValidationManager * This is only meant to be used by the ValidationManager.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -337,8 +346,8 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Returns the status of all messages in the given group with respect to * Returns the status of all delivered messages in the given group with
* the given contact. * respect to the given contact.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -373,8 +382,8 @@ public interface DatabaseComponent {
State getMessageState(Transaction txn, MessageId m) throws DbException; State getMessageState(Transaction txn, MessageId m) throws DbException;
/** /**
* Returns the status of the given message with respect to the given * Returns the status of the given delivered message with respect to the
* contact. * given contact.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.keyagreement.event;
import org.briarproject.bramble.api.event.Event;
/**
* An event that is broadcast when a BQP task stops listening.
*/
public class KeyAgreementStoppedListeningEvent extends Event {
}

View File

@@ -43,17 +43,20 @@ public interface LifecycleManager {
} }
/** /**
* Registers a {@link Service} to be started and stopped. * Registers a {@link Service} to be started and stopped. This method
* should be called before {@link #startServices(String)}.
*/ */
void registerService(Service s); void registerService(Service s);
/** /**
* Registers a {@link Client} to be started. * Registers a {@link Client} to be started. This method should be called
* before {@link #startServices(String)}.
*/ */
void registerClient(Client c); void registerClient(Client c);
/** /**
* Registers an {@link ExecutorService} to be shut down. * Registers an {@link ExecutorService} to be shut down. This method
* should be called before {@link #startServices(String)}.
*/ */
void registerForShutdown(ExecutorService e); void registerForShutdown(ExecutorService e);

View File

@@ -15,12 +15,17 @@ public interface TransportPropertyManager {
/** /**
* The unique ID of the transport property client. * The unique ID of the transport property client.
*/ */
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties"); ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.properties");
/** /**
* The current version of the transport property client. * The current major version of the transport property client.
*/ */
int CLIENT_VERSION = 0; int MAJOR_VERSION = 0;
/**
* The current minor version of the transport property client.
*/
int MINOR_VERSION = 0;
/** /**
* Stores the given properties received while adding a contact - they will * Stores the given properties received while adding a contact - they will
@@ -37,8 +42,8 @@ public interface TransportPropertyManager {
/** /**
* Returns the local transport properties for all transports. * Returns the local transport properties for all transports.
* <br/> * <p/>
* TODO: Transaction can be read-only when code is simplified * Read-only.
*/ */
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn) Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
throws DbException; throws DbException;

View File

@@ -0,0 +1,36 @@
package org.briarproject.bramble.api.record;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class Record {
public static final int RECORD_HEADER_BYTES = 4;
public static final int MAX_RECORD_PAYLOAD_BYTES = 48 * 1024; // 48 KiB
private final byte protocolVersion, recordType;
private final byte[] payload;
public Record(byte protocolVersion, byte recordType, byte[] payload) {
if (payload.length > MAX_RECORD_PAYLOAD_BYTES)
throw new IllegalArgumentException();
this.protocolVersion = protocolVersion;
this.recordType = recordType;
this.payload = payload;
}
public byte getProtocolVersion() {
return protocolVersion;
}
public byte getRecordType() {
return recordType;
}
public byte[] getPayload() {
return payload;
}
}

View File

@@ -0,0 +1,20 @@
package org.briarproject.bramble.api.record;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.EOFException;
import java.io.IOException;
@NotNullByDefault
public interface RecordReader {
/**
* Reads and returns the next record.
*
* @throws EOFException if the end of the stream is reached without reading
* a complete record
*/
Record readRecord() throws IOException;
void close() throws IOException;
}

View File

@@ -0,0 +1,8 @@
package org.briarproject.bramble.api.record;
import java.io.InputStream;
public interface RecordReaderFactory {
RecordReader createRecordReader(InputStream in);
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.record;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
@NotNullByDefault
public interface RecordWriter {
void writeRecord(Record r) throws IOException;
void flush() throws IOException;
void close() throws IOException;
}

View File

@@ -0,0 +1,8 @@
package org.briarproject.bramble.api.record;
import java.io.OutputStream;
public interface RecordWriterFactory {
RecordWriter createRecordWriter(OutputStream out);
}

View File

@@ -5,9 +5,24 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPT
public class Group { public class Group {
public enum Visibility { public enum Visibility {
INVISIBLE, // The group is not visible
VISIBLE, // The group is visible but messages are not shared INVISIBLE(0), // The group is not visible
SHARED // The group is visible and messages are shared VISIBLE(1), // The group is visible, messages are accepted but not sent
SHARED(2); // The group is visible, messages are accepted and sent
private final int value;
Visibility(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static Visibility min(Visibility a, Visibility b) {
return a.getValue() < b.getValue() ? a : b;
}
} }
/** /**
@@ -17,13 +32,16 @@ public class Group {
private final GroupId id; private final GroupId id;
private final ClientId clientId; private final ClientId clientId;
private final int majorVersion;
private final byte[] descriptor; private final byte[] descriptor;
public Group(GroupId id, ClientId clientId, byte[] descriptor) { public Group(GroupId id, ClientId clientId, int majorVersion,
byte[] descriptor) {
if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH) if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
this.id = id; this.id = id;
this.clientId = clientId; this.clientId = clientId;
this.majorVersion = majorVersion;
this.descriptor = descriptor; this.descriptor = descriptor;
} }
@@ -41,6 +59,13 @@ public class Group {
return clientId; return clientId;
} }
/**
* Returns the major version of the client to which the group belongs.
*/
public int getMajorVersion() {
return majorVersion;
}
/** /**
* Returns the group's descriptor. * Returns the group's descriptor.
*/ */

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
public interface GroupFactory { public interface GroupFactory {
/** /**
* Creates a group with the given client ID, client version and descriptor. * Creates a group with the given client ID, major version and descriptor.
*/ */
Group createGroup(ClientId c, int clientVersion, byte[] descriptor); Group createGroup(ClientId c, int majorVersion, byte[] descriptor);
} }

View File

@@ -7,5 +7,7 @@ public interface MessageFactory {
Message createMessage(GroupId g, long timestamp, byte[] body); Message createMessage(GroupId g, long timestamp, byte[] body);
Message createMessage(byte[] raw);
Message createMessage(MessageId m, byte[] raw); Message createMessage(MessageId m, byte[] raw);
} }

View File

@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
public interface SyncConstants { public interface SyncConstants {
/** /**
@@ -10,16 +12,8 @@ public interface SyncConstants {
byte PROTOCOL_VERSION = 0; byte PROTOCOL_VERSION = 0;
/** /**
* The length of the record header in bytes. * The maximum length of a group descriptor in bytes.
*/ */
int RECORD_HEADER_LENGTH = 4;
/**
* The maximum length of the record payload in bytes.
*/
int MAX_RECORD_PAYLOAD_LENGTH = 48 * 1024; // 48 KiB
/** The maximum length of a group descriptor in bytes. */
int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB
/** /**
@@ -40,5 +34,5 @@ public interface SyncConstants {
/** /**
* The maximum number of message IDs in an ack, offer or request record. * The maximum number of message IDs in an ack, offer or request record.
*/ */
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_LENGTH / UniqueId.LENGTH; int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
} }

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
@NotNullByDefault @NotNullByDefault
public interface RecordReader { public interface SyncRecordReader {
boolean eof() throws IOException; boolean eof() throws IOException;

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream; import java.io.InputStream;
@NotNullByDefault @NotNullByDefault
public interface RecordReaderFactory { public interface SyncRecordReaderFactory {
RecordReader createRecordReader(InputStream in); SyncRecordReader createRecordReader(InputStream in);
} }

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
@NotNullByDefault @NotNullByDefault
public interface RecordWriter { public interface SyncRecordWriter {
void writeAck(Ack a) throws IOException; void writeAck(Ack a) throws IOException;

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.OutputStream; import java.io.OutputStream;
@NotNullByDefault @NotNullByDefault
public interface RecordWriterFactory { public interface SyncRecordWriterFactory {
RecordWriter createRecordWriter(OutputStream out); SyncRecordWriter createRecordWriter(OutputStream out);
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.sync;
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.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/** /**
@@ -33,15 +34,20 @@ public interface ValidationManager {
} }
/** /**
* Sets the message validator for the given client. * Registers the message validator for the given client. This method
* should be called before {@link LifecycleManager#startServices(String)}.
*/ */
void registerMessageValidator(ClientId c, MessageValidator v); void registerMessageValidator(ClientId c, int majorVersion,
MessageValidator v);
/** /**
* Sets the incoming message hook for the given client. The hook will be * Registers the incoming message hook for the given client. The hook will
* called once for each incoming message that passes validation. * be called once for each incoming message that passes validation. This
* method should be called before
* {@link LifecycleManager#startServices(String)}.
*/ */
void registerIncomingMessageHook(ClientId c, IncomingMessageHook hook); void registerIncomingMessageHook(ClientId c, int majorVersion,
IncomingMessageHook hook);
interface MessageValidator { interface MessageValidator {

View File

@@ -23,6 +23,8 @@ public interface KeyManager {
* <p/> * <p/>
* {@link StreamContext StreamContexts} for the contact can be created * {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned. * after this method has returned.
*
* @param alice true if the local party is Alice
*/ */
void addContact(Transaction txn, ContactId c, SecretKey master, void addContact(Transaction txn, ContactId c, SecretKey master,
long timestamp, boolean alice) throws DbException; long timestamp, boolean alice) throws DbException;
@@ -33,6 +35,8 @@ public interface KeyManager {
* <p/> * <p/>
* The keys must be bound before they can be used for incoming streams, * The keys must be bound before they can be used for incoming streams,
* and also activated before they can be used for outgoing streams. * and also activated before they can be used for outgoing streams.
*
* @param alice true if the local party is Alice
*/ */
Map<TransportId, KeySetId> addUnboundKeys(Transaction txn, SecretKey master, Map<TransportId, KeySetId> addUnboundKeys(Transaction txn, SecretKey master,
long timestamp, boolean alice) throws DbException; long timestamp, boolean alice) throws DbException;
@@ -55,7 +59,7 @@ public interface KeyManager {
* the manager and the database. * the manager and the database.
*/ */
void removeKeys(Transaction txn, Map<TransportId, KeySetId> keys) void removeKeys(Transaction txn, Map<TransportId, KeySetId> keys)
throws DbException; throws DbException;
/** /**
* Returns true if we have keys that can be used for outgoing streams to * Returns true if we have keys that can be used for outgoing streams to

View File

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

View File

@@ -0,0 +1,45 @@
package org.briarproject.bramble.api.versioning;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group.Visibility;
@NotNullByDefault
public interface ClientVersioningManager {
/**
* The unique ID of the versioning client.
*/
ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.versioning");
/**
* The current major version of the versioning client.
*/
int MAJOR_VERSION = 0;
/**
* Registers a client that will be advertised to contacts. The hook will
* be called when the visibility of the client changes. This method should
* be called before {@link LifecycleManager#startServices(String)}.
*/
void registerClient(ClientId clientId, int majorVersion, int minorVersion,
ClientVersioningHook hook);
/**
* Returns the visibility of the given client with respect to the given
* contact.
*/
Visibility getClientVisibility(Transaction txn, ContactId contactId,
ClientId clientId, int majorVersion) throws DbException;
interface ClientVersioningHook {
void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException;
}
}

View File

@@ -117,15 +117,16 @@ public class TestUtils {
return new Author(id, FORMAT_VERSION, name, publicKey); return new Author(id, FORMAT_VERSION, name, publicKey);
} }
public static Group getGroup(ClientId clientId) { public static Group getGroup(ClientId clientId, int majorVersion) {
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH); int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
return getGroup(clientId, descriptorLength); return getGroup(clientId, majorVersion, descriptorLength);
} }
public static Group getGroup(ClientId clientId, int descriptorLength) { public static Group getGroup(ClientId clientId, int majorVersion,
int descriptorLength) {
GroupId groupId = new GroupId(getRandomId()); GroupId groupId = new GroupId(getRandomId());
byte[] descriptor = getRandomBytes(descriptorLength); byte[] descriptor = getRandomBytes(descriptorLength);
return new Group(groupId, clientId, descriptor); return new Group(groupId, clientId, majorVersion, descriptor);
} }
public static Message getMessage(GroupId groupId) { public static Message getMessage(GroupId groupId) {

View File

@@ -31,7 +31,6 @@ dependencies {
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.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3', 'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b', 'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9', 'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble; package org.briarproject.bramble;
import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.contact.ContactModule;
import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.db.DatabaseExecutorModule; import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
@@ -10,12 +10,13 @@ import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule;
public interface BrambleCoreEagerSingletons { public interface BrambleCoreEagerSingletons {
void inject(ContactModule.EagerSingletons init); void inject(ContactModule.EagerSingletons init);
void inject(CryptoModule.EagerSingletons init); void inject(CryptoExecutorModule.EagerSingletons init);
void inject(DatabaseExecutorModule.EagerSingletons init); void inject(DatabaseExecutorModule.EagerSingletons init);
@@ -32,4 +33,6 @@ public interface BrambleCoreEagerSingletons {
void inject(SystemModule.EagerSingletons init); void inject(SystemModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init); void inject(TransportModule.EagerSingletons init);
void inject(VersioningModule.EagerSingletons init);
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble;
import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.client.ClientModule;
import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.contact.ContactModule;
import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.crypto.CryptoModule;
import org.briarproject.bramble.data.DataModule; import org.briarproject.bramble.data.DataModule;
import org.briarproject.bramble.db.DatabaseExecutorModule; import org.briarproject.bramble.db.DatabaseExecutorModule;
@@ -12,6 +13,7 @@ import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.reliability.ReliabilityModule; import org.briarproject.bramble.reliability.ReliabilityModule;
import org.briarproject.bramble.reporting.ReportingModule; import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.settings.SettingsModule; import org.briarproject.bramble.settings.SettingsModule;
@@ -19,6 +21,7 @@ import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule;
import dagger.Module; import dagger.Module;
@@ -26,6 +29,7 @@ import dagger.Module;
ClientModule.class, ClientModule.class,
ContactModule.class, ContactModule.class,
CryptoModule.class, CryptoModule.class,
CryptoExecutorModule.class,
DataModule.class, DataModule.class,
DatabaseModule.class, DatabaseModule.class,
DatabaseExecutorModule.class, DatabaseExecutorModule.class,
@@ -35,19 +39,21 @@ import dagger.Module;
LifecycleModule.class, LifecycleModule.class,
PluginModule.class, PluginModule.class,
PropertiesModule.class, PropertiesModule.class,
RecordModule.class,
ReliabilityModule.class, ReliabilityModule.class,
ReportingModule.class, ReportingModule.class,
SettingsModule.class, SettingsModule.class,
SocksModule.class, SocksModule.class,
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,
TransportModule.class TransportModule.class,
VersioningModule.class
}) })
public class BrambleCoreModule { public class BrambleCoreModule {
public static void initEagerSingletons(BrambleCoreEagerSingletons c) { public static void initEagerSingletons(BrambleCoreEagerSingletons c) {
c.inject(new ContactModule.EagerSingletons()); c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoModule.EagerSingletons()); c.inject(new CryptoExecutorModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons()); c.inject(new DatabaseExecutorModule.EagerSingletons());
c.inject(new IdentityModule.EagerSingletons()); c.inject(new IdentityModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons()); c.inject(new LifecycleModule.EagerSingletons());
@@ -56,5 +62,6 @@ public class BrambleCoreModule {
c.inject(new SyncModule.EagerSingletons()); c.inject(new SyncModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons()); c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons());
} }
} }

View File

@@ -32,25 +32,25 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
} }
@Override @Override
public Group createLocalGroup(ClientId clientId, int clientVersion) { public Group createLocalGroup(ClientId clientId, int majorVersion) {
return groupFactory.createGroup(clientId, clientVersion, return groupFactory.createGroup(clientId, majorVersion,
LOCAL_GROUP_DESCRIPTOR); LOCAL_GROUP_DESCRIPTOR);
} }
@Override @Override
public Group createContactGroup(ClientId clientId, int clientVersion, public Group createContactGroup(ClientId clientId, int majorVersion,
Contact contact) { Contact contact) {
AuthorId local = contact.getLocalAuthorId(); 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, clientVersion, descriptor); return groupFactory.createGroup(clientId, majorVersion, descriptor);
} }
@Override @Override
public Group createContactGroup(ClientId clientId, int clientVersion, public Group createContactGroup(ClientId clientId, int majorVersion,
AuthorId authorId1, AuthorId authorId2) { AuthorId authorId1, AuthorId authorId2) {
byte[] descriptor = createGroupDescriptor(authorId1, authorId2); byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
return groupFactory.createGroup(clientId, clientVersion, descriptor); return groupFactory.createGroup(clientId, majorVersion, descriptor);
} }
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) { private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {

View File

@@ -1,23 +1,20 @@
package org.briarproject.bramble.contact; package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactExchangeListener; import org.briarproject.bramble.api.contact.ContactExchangeListener;
import org.briarproject.bramble.api.contact.ContactExchangeTask; import org.briarproject.bramble.api.contact.ContactExchangeTask;
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.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.data.BdfDictionary;
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.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.ContactExistsException;
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.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.AuthorFactory;
import org.briarproject.bramble.api.identity.LocalAuthor; 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;
@@ -26,30 +23,30 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
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.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -62,9 +59,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
"org.briarproject.briar.contact/EXCHANGE"; "org.briarproject.briar.contact/EXCHANGE";
private final DatabaseComponent db; private final DatabaseComponent db;
private final AuthorFactory authorFactory; private final ClientHelper clientHelper;
private final BdfReaderFactory bdfReaderFactory; private final RecordReaderFactory recordReaderFactory;
private final BdfWriterFactory bdfWriterFactory; private final RecordWriterFactory recordWriterFactory;
private final Clock clock; private final Clock clock;
private final ConnectionManager connectionManager; private final ConnectionManager connectionManager;
private final ContactManager contactManager; private final ContactManager contactManager;
@@ -81,17 +78,17 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private volatile boolean alice; private volatile boolean alice;
@Inject @Inject
ContactExchangeTaskImpl(DatabaseComponent db, ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper,
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory, RecordReaderFactory recordReaderFactory,
BdfWriterFactory bdfWriterFactory, Clock clock, RecordWriterFactory recordWriterFactory, Clock clock,
ConnectionManager connectionManager, ContactManager contactManager, ConnectionManager connectionManager, ContactManager contactManager,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
CryptoComponent crypto, StreamReaderFactory streamReaderFactory, CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory) { StreamWriterFactory streamWriterFactory) {
this.db = db; this.db = db;
this.authorFactory = authorFactory; this.clientHelper = clientHelper;
this.bdfReaderFactory = bdfReaderFactory; this.recordReaderFactory = recordReaderFactory;
this.bdfWriterFactory = bdfWriterFactory; this.recordWriterFactory = recordWriterFactory;
this.clock = clock; this.clock = clock;
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.contactManager = contactManager; this.contactManager = contactManager;
@@ -126,18 +123,18 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
listener.contactExchangeFailed(); listener.contactExchangeFailed();
tryToClose(conn, true); tryToClose(conn);
return; return;
} }
// Get the local transport properties // Get the local transport properties
Map<TransportId, TransportProperties> localProperties, remoteProperties; Map<TransportId, TransportProperties> localProperties;
try { try {
localProperties = transportPropertyManager.getLocalProperties(); localProperties = transportPropertyManager.getLocalProperties();
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
listener.contactExchangeFailed(); listener.contactExchangeFailed();
tryToClose(conn, true); tryToClose(conn);
return; return;
} }
@@ -151,159 +148,138 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
InputStream streamReader = InputStream streamReader =
streamReaderFactory.createContactExchangeStreamReader(in, streamReaderFactory.createContactExchangeStreamReader(in,
alice ? bobHeaderKey : aliceHeaderKey); alice ? bobHeaderKey : aliceHeaderKey);
BdfReader r = bdfReaderFactory.createReader(streamReader); RecordReader recordReader =
recordReaderFactory.createRecordReader(streamReader);
// Create the writers // Create the writers
OutputStream streamWriter = OutputStream streamWriter =
streamWriterFactory.createContactExchangeStreamWriter(out, streamWriterFactory.createContactExchangeStreamWriter(out,
alice ? aliceHeaderKey : bobHeaderKey); alice ? aliceHeaderKey : bobHeaderKey);
BdfWriter w = bdfWriterFactory.createWriter(streamWriter); RecordWriter recordWriter =
recordWriterFactory.createRecordWriter(streamWriter);
// Derive the nonces to be signed // Derive the nonces to be signed
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret, byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION}); new byte[] {PROTOCOL_VERSION});
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret, byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION}); new byte[] {PROTOCOL_VERSION});
byte[] localNonce = alice ? aliceNonce : bobNonce;
byte[] remoteNonce = alice ? bobNonce : aliceNonce;
// Exchange pseudonyms, signed nonces, and timestamps // Sign the nonce
byte[] localSignature = sign(localAuthor, localNonce);
// Exchange contact info
long localTimestamp = clock.currentTimeMillis(); long localTimestamp = clock.currentTimeMillis();
Author remoteAuthor; ContactInfo remoteInfo;
long remoteTimestamp;
try { try {
if (alice) { if (alice) {
sendPseudonym(w, aliceNonce); sendContactInfo(recordWriter, localAuthor, localProperties,
sendTimestamp(w, localTimestamp); localSignature, localTimestamp);
sendTransportProperties(w, localProperties); recordWriter.flush();
w.flush(); remoteInfo = receiveContactInfo(recordReader);
remoteAuthor = receivePseudonym(r, bobNonce);
remoteTimestamp = receiveTimestamp(r);
remoteProperties = receiveTransportProperties(r);
} else { } else {
remoteAuthor = receivePseudonym(r, aliceNonce); remoteInfo = receiveContactInfo(recordReader);
remoteTimestamp = receiveTimestamp(r); sendContactInfo(recordWriter, localAuthor, localProperties,
remoteProperties = receiveTransportProperties(r); localSignature, localTimestamp);
sendPseudonym(w, bobNonce); recordWriter.flush();
sendTimestamp(w, localTimestamp);
sendTransportProperties(w, localProperties);
w.flush();
} }
// Close the outgoing stream and expect EOF on the incoming stream // Close the outgoing stream
w.close(); recordWriter.close();
if (!r.eof()) LOG.warning("Unexpected data at end of connection"); // Skip any remaining records from the incoming stream
} catch (GeneralSecurityException | IOException e) { try {
while (true) recordReader.readRecord();
} catch (EOFException expected) {
LOG.info("End of stream");
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
listener.contactExchangeFailed(); listener.contactExchangeFailed();
tryToClose(conn, true); tryToClose(conn);
return;
}
// Verify the contact's signature
if (!verify(remoteInfo.author, remoteNonce, remoteInfo.signature)) {
LOG.warning("Invalid signature");
listener.contactExchangeFailed();
tryToClose(conn);
return; return;
} }
// The agreed timestamp is the minimum of the peers' timestamps // The agreed timestamp is the minimum of the peers' timestamps
long timestamp = Math.min(localTimestamp, remoteTimestamp); long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
try { try {
// Add the contact // Add the contact
ContactId contactId = addContact(remoteAuthor, timestamp, ContactId contactId = addContact(remoteInfo.author, timestamp,
remoteProperties); remoteInfo.properties);
// Reuse the connection as a transport connection // Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contactId, transportId, connectionManager.manageOutgoingConnection(contactId, transportId,
conn); conn);
// Pseudonym exchange succeeded // Pseudonym exchange succeeded
LOG.info("Pseudonym exchange succeeded"); LOG.info("Pseudonym exchange succeeded");
listener.contactExchangeSucceeded(remoteAuthor); listener.contactExchangeSucceeded(remoteInfo.author);
} catch (ContactExistsException e) { } catch (ContactExistsException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true); tryToClose(conn);
listener.duplicateContact(remoteAuthor); listener.duplicateContact(remoteInfo.author);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true); tryToClose(conn);
listener.contactExchangeFailed(); listener.contactExchangeFailed();
} }
} }
private void sendPseudonym(BdfWriter w, byte[] nonce) private byte[] sign(LocalAuthor author, byte[] nonce) {
throws GeneralSecurityException, IOException { try {
// Sign the nonce return crypto.sign(SIGNING_LABEL_EXCHANGE, nonce,
byte[] privateKey = localAuthor.getPrivateKey(); author.getPrivateKey());
byte[] sig = crypto.sign(SIGNING_LABEL_EXCHANGE, nonce, privateKey); } catch (GeneralSecurityException e) {
throw new AssertionError();
// Write the name, public key and signature
w.writeListStart();
w.writeLong(localAuthor.getFormatVersion());
w.writeString(localAuthor.getName());
w.writeRaw(localAuthor.getPublicKey());
w.writeRaw(sig);
w.writeListEnd();
LOG.info("Sent pseudonym");
}
private Author receivePseudonym(BdfReader r, byte[] nonce)
throws GeneralSecurityException, IOException {
// Read the format version, name, public key and signature
r.readListStart();
int formatVersion = (int) r.readLong();
if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
if (name.isEmpty()) throw new FormatException();
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
if (publicKey.length == 0) throw new FormatException();
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
if (sig.length == 0) throw new FormatException();
r.readListEnd();
LOG.info("Received pseudonym");
// Verify the signature
if (!crypto.verifySignature(sig, SIGNING_LABEL_EXCHANGE, nonce,
publicKey)) {
if (LOG.isLoggable(INFO))
LOG.info("Invalid signature");
throw new GeneralSecurityException();
} }
return authorFactory.createAuthor(formatVersion, name, publicKey);
} }
private void sendTimestamp(BdfWriter w, long timestamp) private boolean verify(Author author, byte[] nonce, byte[] signature) {
try {
return crypto.verifySignature(signature, SIGNING_LABEL_EXCHANGE,
nonce, author.getPublicKey());
} catch (GeneralSecurityException e) {
return false;
}
}
private void sendContactInfo(RecordWriter recordWriter, Author author,
Map<TransportId, TransportProperties> properties, byte[] signature,
long timestamp) throws IOException {
BdfList authorList = clientHelper.toList(author);
BdfDictionary props = clientHelper.toDictionary(properties);
BdfList payload = BdfList.of(authorList, props, signature, timestamp);
recordWriter.writeRecord(new Record(PROTOCOL_VERSION, CONTACT_INFO,
clientHelper.toByteArray(payload)));
LOG.info("Sent contact info");
}
private ContactInfo receiveContactInfo(RecordReader recordReader)
throws IOException { throws IOException {
w.writeLong(timestamp); Record record;
LOG.info("Sent timestamp"); do {
} record = recordReader.readRecord();
if (record.getProtocolVersion() != PROTOCOL_VERSION)
private long receiveTimestamp(BdfReader r) throws IOException { throw new FormatException();
long timestamp = r.readLong(); } while (record.getRecordType() != CONTACT_INFO);
LOG.info("Received contact info");
BdfList payload = clientHelper.toList(record.getPayload());
checkSize(payload, 4);
Author author = clientHelper.parseAndValidateAuthor(payload.getList(0));
BdfDictionary props = payload.getDictionary(1);
Map<TransportId, TransportProperties> properties =
clientHelper.parseAndValidateTransportPropertiesMap(props);
byte[] signature = payload.getRaw(2);
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
long timestamp = payload.getLong(3);
if (timestamp < 0) throw new FormatException(); if (timestamp < 0) throw new FormatException();
LOG.info("Received timestamp"); return new ContactInfo(author, properties, signature, timestamp);
return timestamp;
}
private void sendTransportProperties(BdfWriter w,
Map<TransportId, TransportProperties> local) throws IOException {
w.writeListStart();
for (Entry<TransportId, TransportProperties> e : local.entrySet())
w.writeList(BdfList.of(e.getKey().getString(), e.getValue()));
w.writeListEnd();
}
private Map<TransportId, TransportProperties> receiveTransportProperties(
BdfReader r) throws IOException {
Map<TransportId, TransportProperties> remote = new HashMap<>();
r.readListStart();
while (!r.hasListEnd()) {
r.readListStart();
String id = r.readString(MAX_TRANSPORT_ID_LENGTH);
if (id.isEmpty()) throw new FormatException();
TransportProperties p = new TransportProperties();
r.readDictionaryStart();
while (!r.hasDictionaryEnd()) {
if (p.size() == MAX_PROPERTIES_PER_TRANSPORT)
throw new FormatException();
String key = r.readString(MAX_PROPERTY_LENGTH);
String value = r.readString(MAX_PROPERTY_LENGTH);
p.put(key, value);
}
r.readDictionaryEnd();
r.readListEnd();
remote.put(new TransportId(id), p);
}
r.readListEnd();
return remote;
} }
private ContactId addContact(Author remoteAuthor, long timestamp, private ContactId addContact(Author remoteAuthor, long timestamp,
@@ -324,13 +300,30 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
return contactId; return contactId;
} }
private void tryToClose(DuplexTransportConnection conn, boolean exception) { private void tryToClose(DuplexTransportConnection conn) {
try { try {
LOG.info("Closing connection"); LOG.info("Closing connection");
conn.getReader().dispose(exception, true); conn.getReader().dispose(true, true);
conn.getWriter().dispose(exception); conn.getWriter().dispose(true);
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
} }
private static class ContactInfo {
private final Author author;
private final Map<TransportId, TransportProperties> properties;
private final byte[] signature;
private final long timestamp;
private ContactInfo(Author author,
Map<TransportId, TransportProperties> properties,
byte[] signature, long timestamp) {
this.author = author;
this.properties = properties;
this.signature = signature;
this.timestamp = timestamp;
}
}
} }

View File

@@ -0,0 +1,67 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static java.util.concurrent.TimeUnit.SECONDS;
@Module
public class CryptoExecutorModule {
public static class EagerSingletons {
@Inject
@CryptoExecutor
ExecutorService cryptoExecutor;
}
/**
* The maximum number of executor threads.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_EXECUTOR_THREADS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
private final ExecutorService cryptoExecutor;
public CryptoExecutorModule() {
// Use an unbounded queue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create a limited # of threads and keep them in the pool for 60 secs
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
}
@Provides
@Singleton
@CryptoExecutor
ExecutorService provideCryptoExecutorService(
LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(cryptoExecutor);
return cryptoExecutor;
}
@Provides
@CryptoExecutor
Executor provideCryptoExecutor() {
return cryptoExecutor;
}
}

View File

@@ -1,64 +1,24 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto; import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory; import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory; import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static java.util.concurrent.TimeUnit.SECONDS;
@Module @Module
public class CryptoModule { public class CryptoModule {
public static class EagerSingletons {
@Inject
@CryptoExecutor
ExecutorService cryptoExecutor;
}
/**
* The maximum number of executor threads.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_EXECUTOR_THREADS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
private final ExecutorService cryptoExecutor;
public CryptoModule() {
// Use an unbounded queue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create a limited # of threads and keep them in the pool for 60 secs
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
}
@Provides @Provides
AuthenticatedCipher provideAuthenticatedCipher() { AuthenticatedCipher provideAuthenticatedCipher() {
return new XSalsa20Poly1305AuthenticatedCipher(); return new XSalsa20Poly1305AuthenticatedCipher();
@@ -103,21 +63,6 @@ public class CryptoModule {
return keyAgreementCrypto; return keyAgreementCrypto;
} }
@Provides
@Singleton
@CryptoExecutor
ExecutorService getCryptoExecutorService(
LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(cryptoExecutor);
return cryptoExecutor;
}
@Provides
@CryptoExecutor
Executor getCryptoExecutor() {
return cryptoExecutor;
}
@Provides @Provides
SecureRandom getSecureRandom(CryptoComponent crypto) { SecureRandom getSecureRandom(CryptoComponent crypto) {
return crypto.getSecureRandom(); return crypto.getSecureRandom();

View File

@@ -266,7 +266,8 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<Group> getGroups(T txn, ClientId c) throws DbException; Collection<Group> getGroups(T txn, ClientId c, int majorVersion)
throws DbException;
/** /**
* Returns the given group's visibility to the given contact, or * Returns the given group's visibility to the given contact, or
@@ -321,16 +322,16 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs of all messages in the given group. * Returns the IDs of all delivered messages in the given group.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException; Collection<MessageId> getMessageIds(T txn, GroupId g) throws DbException;
/** /**
* Returns the IDs of any messages in the given group with metadata * Returns the IDs of any delivered messages in the given group with
* matching all entries in the given query. If the query is empty, the IDs * metadata that matches all entries in the given query. If the query is
* of all messages are returned. * empty, the IDs of all delivered messages are returned.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -346,9 +347,9 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the metadata for any messages in the given group with metadata * Returns the metadata for any delivered messages in the given group with
* matching all entries in the given query. If the query is empty, the * metadata that matches all entries in the given query. If the query is
* metadata for all messages is returned. * empty, the metadata for all delivered messages is returned.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -356,7 +357,8 @@ interface Database<T> {
Metadata query) throws DbException; Metadata query) throws DbException;
/** /**
* Returns the metadata for the given delivered message. * Returns the metadata for the given delivered or pending message.
* This is only meant to be used by the ValidationManager.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -364,7 +366,7 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the metadata for the given message. * Returns the metadata for the given delivered message.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -378,8 +380,8 @@ interface Database<T> {
State getMessageState(T txn, MessageId m) throws DbException; State getMessageState(T txn, MessageId m) throws DbException;
/** /**
* Returns the status of all messages in the given group with respect * Returns the status of all delivered messages in the given group with
* to the given contact. * respect to the given contact.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@@ -387,11 +389,13 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the status of the given message with respect to the given * Returns the status of the given delivered message with respect to the
* given contact, or null if the message's group is invisible to the
* contact. * contact.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
@Nullable
MessageStatus getMessageStatus(T txn, ContactId c, MessageId m) MessageStatus getMessageStatus(T txn, ContactId c, MessageId m)
throws DbException; throws DbException;
@@ -654,7 +658,7 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Stores the given transport keys, deleting any keys they have replaced. * Updates the given transport keys following key rotation.
*/ */
void updateTransportKeys(T txn, Collection<KeySet> keys) throws DbException; void updateTransportKeys(T txn, KeySet ks) throws DbException;
} }

View File

@@ -435,10 +435,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Collection<Group> getGroups(Transaction transaction, ClientId c) public Collection<Group> getGroups(Transaction transaction, ClientId c,
throws DbException { int majorVersion) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getGroups(txn, c); return db.getGroups(txn, c, majorVersion);
} }
@Override @Override
@@ -466,6 +466,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getLocalAuthors(txn); return db.getLocalAuthors(txn);
} }
@Override
public Collection<MessageId> getMessageIds(Transaction transaction,
GroupId g) throws DbException {
T txn = unbox(transaction);
if (!db.containsGroup(txn, g))
throw new NoSuchGroupException();
return db.getMessageIds(txn, g);
}
@Override @Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction) public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException { throws DbException {
@@ -551,6 +560,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException(); throw new NoSuchContactException();
if (!db.containsGroup(txn, g)) if (!db.containsGroup(txn, g))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
if (db.getGroupVisibility(txn, c, g) == INVISIBLE) {
// No status rows exist - return default statuses
Collection<MessageStatus> statuses = new ArrayList<>();
for (MessageId m : db.getMessageIds(txn, g))
statuses.add(new MessageStatus(m, c, false, false));
return statuses;
}
return db.getMessageStatus(txn, c, g); return db.getMessageStatus(txn, c, g);
} }
@@ -562,7 +578,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException(); throw new NoSuchContactException();
if (!db.containsMessage(txn, m)) if (!db.containsMessage(txn, m))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
return db.getMessageStatus(txn, c, m); MessageStatus status = db.getMessageStatus(txn, c, m);
if (status == null) return new MessageStatus(m, c, false, false);
return status;
} }
@Override @Override
@@ -903,11 +921,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
Collection<KeySet> keys) throws DbException { Collection<KeySet> keys) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
Collection<KeySet> filtered = new ArrayList<>();
for (KeySet ks : keys) { for (KeySet ks : keys) {
TransportId t = ks.getTransportKeys().getTransportId(); TransportId t = ks.getTransportKeys().getTransportId();
if (db.containsTransport(txn, t)) filtered.add(ks); if (db.containsTransport(txn, t)) db.updateTransportKeys(txn, ks);
} }
db.updateTransportKeys(txn, filtered);
} }
} }

View File

@@ -74,7 +74,12 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
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 = 36; static final int CODE_SCHEMA_VERSION = 38;
// Rotation period offsets for incoming transport keys
private static final int OFFSET_PREV = -1;
private static final int OFFSET_CURR = 0;
private static final int OFFSET_NEXT = 1;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"
@@ -112,6 +117,7 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE TABLE groups" "CREATE TABLE groups"
+ " (groupId _HASH NOT NULL," + " (groupId _HASH NOT NULL,"
+ " clientId _STRING NOT NULL," + " clientId _STRING NOT NULL,"
+ " majorVersion INT NOT NULL,"
+ " descriptor _BINARY NOT NULL," + " descriptor _BINARY NOT NULL,"
+ " PRIMARY KEY (groupId))"; + " PRIMARY KEY (groupId))";
@@ -254,7 +260,8 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " headerKey _SECRET NOT NULL," + " headerKey _SECRET NOT NULL,"
+ " base BIGINT NOT NULL," + " base BIGINT NOT NULL,"
+ " bitmap _BINARY NOT NULL," + " bitmap _BINARY NOT NULL,"
+ " PRIMARY KEY (transportId, keySetId, rotationPeriod)," + " periodOffset INT NOT NULL,"
+ " PRIMARY KEY (transportId, keySetId, periodOffset),"
+ " FOREIGN KEY (transportId)" + " FOREIGN KEY (transportId)"
+ " REFERENCES transports (transportId)" + " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE," + " ON DELETE CASCADE,"
@@ -269,9 +276,9 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS contactsByAuthorId" "CREATE INDEX IF NOT EXISTS contactsByAuthorId"
+ " ON contacts (authorId)"; + " ON contacts (authorId)";
private static final String INDEX_GROUPS_BY_CLIENT_ID = private static final String INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION =
"CREATE INDEX IF NOT EXISTS groupsByClientId" "CREATE INDEX IF NOT EXISTS groupsByClientIdMajorVersion"
+ " ON groups (clientId)"; + " ON groups (clientId, majorVersion)";
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE = private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState" "CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
@@ -438,7 +445,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
s = txn.createStatement(); s = txn.createStatement();
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID); s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID); s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION);
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE); s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID); s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
@@ -606,12 +613,14 @@ abstract class JdbcDatabase implements Database<Connection> {
public void addGroup(Connection txn, Group g) throws DbException { public void addGroup(Connection txn, Group g) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO groups (groupId, clientId, descriptor)" String sql = "INSERT INTO groups"
+ " VALUES (?, ?, ?)"; + " (groupId, clientId, majorVersion, descriptor)"
+ " VALUES (?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getId().getBytes()); ps.setBytes(1, g.getId().getBytes());
ps.setString(2, g.getClientId().getString()); ps.setString(2, g.getClientId().getString());
ps.setBytes(3, g.getDescriptor()); ps.setInt(3, g.getMajorVersion());
ps.setBytes(4, g.getDescriptor());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -908,8 +917,9 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
// Store the incoming keys // Store the incoming keys
sql = "INSERT INTO incomingKeys (keySetId, contactId, transportId," sql = "INSERT INTO incomingKeys (keySetId, contactId, transportId,"
+ " rotationPeriod, tagKey, headerKey, base, bitmap)" + " rotationPeriod, tagKey, headerKey, base, bitmap,"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + " periodOffset)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, keySetId.getInt()); ps.setInt(1, keySetId.getInt());
if (c == null) ps.setNull(2, INTEGER); if (c == null) ps.setNull(2, INTEGER);
@@ -922,6 +932,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(6, inPrev.getHeaderKey().getBytes()); ps.setBytes(6, inPrev.getHeaderKey().getBytes());
ps.setLong(7, inPrev.getWindowBase()); ps.setLong(7, inPrev.getWindowBase());
ps.setBytes(8, inPrev.getWindowBitmap()); ps.setBytes(8, inPrev.getWindowBitmap());
ps.setInt(9, OFFSET_PREV);
ps.addBatch(); ps.addBatch();
// Current rotation period // Current rotation period
IncomingKeys inCurr = k.getCurrentIncomingKeys(); IncomingKeys inCurr = k.getCurrentIncomingKeys();
@@ -930,6 +941,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(6, inCurr.getHeaderKey().getBytes()); ps.setBytes(6, inCurr.getHeaderKey().getBytes());
ps.setLong(7, inCurr.getWindowBase()); ps.setLong(7, inCurr.getWindowBase());
ps.setBytes(8, inCurr.getWindowBitmap()); ps.setBytes(8, inCurr.getWindowBitmap());
ps.setInt(9, OFFSET_CURR);
ps.addBatch(); ps.addBatch();
// Next rotation period // Next rotation period
IncomingKeys inNext = k.getNextIncomingKeys(); IncomingKeys inNext = k.getNextIncomingKeys();
@@ -938,6 +950,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(6, inNext.getHeaderKey().getBytes()); ps.setBytes(6, inNext.getHeaderKey().getBytes());
ps.setLong(7, inNext.getWindowBase()); ps.setLong(7, inNext.getWindowBase());
ps.setBytes(8, inNext.getWindowBitmap()); ps.setBytes(8, inNext.getWindowBitmap());
ps.setInt(9, OFFSET_NEXT);
ps.addBatch(); ps.addBatch();
int[] batchAffected = ps.executeBatch(); int[] batchAffected = ps.executeBatch();
if (batchAffected.length != 3) throw new DbStateException(); if (batchAffected.length != 3) throw new DbStateException();
@@ -1336,17 +1349,18 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT clientId, descriptor FROM groups" String sql = "SELECT clientId, majorVersion, descriptor"
+ " WHERE groupId = ?"; + " FROM groups WHERE groupId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException(); if (!rs.next()) throw new DbStateException();
ClientId clientId = new ClientId(rs.getString(1)); ClientId clientId = new ClientId(rs.getString(1));
byte[] descriptor = rs.getBytes(2); int majorVersion = rs.getInt(2);
byte[] descriptor = rs.getBytes(3);
rs.close(); rs.close();
ps.close(); ps.close();
return new Group(g, clientId, descriptor); return new Group(g, clientId, majorVersion, descriptor);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs);
tryToClose(ps); tryToClose(ps);
@@ -1355,21 +1369,22 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<Group> getGroups(Connection txn, ClientId c) public Collection<Group> getGroups(Connection txn, ClientId c,
throws DbException { int majorVersion) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT groupId, descriptor FROM groups" String sql = "SELECT groupId, descriptor FROM groups"
+ " WHERE clientId = ?"; + " WHERE clientId = ? AND majorVersion = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, c.getString()); ps.setString(1, c.getString());
ps.setInt(2, majorVersion);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<Group> groups = new ArrayList<>(); List<Group> groups = new ArrayList<>();
while (rs.next()) { while (rs.next()) {
GroupId id = new GroupId(rs.getBytes(1)); GroupId id = new GroupId(rs.getBytes(1));
byte[] descriptor = rs.getBytes(2); byte[] descriptor = rs.getBytes(2);
groups.add(new Group(id, c, descriptor)); groups.add(new Group(id, c, majorVersion, descriptor));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1501,32 +1516,11 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT messageId FROM messages WHERE groupId = ?"; String sql = "SELECT messageId FROM messages"
+ " WHERE groupId = ? AND state = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
rs = ps.executeQuery(); ps.setInt(2, DELIVERED.getValue());
List<MessageId> ids = new ArrayList<>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close();
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
private Collection<MessageId> getMessageIds(Connection txn, GroupId g,
State state) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId FROM messages"
+ " WHERE state = ? AND groupId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, state.getValue());
ps.setBytes(2, g.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
@@ -1544,7 +1538,7 @@ abstract class JdbcDatabase implements Database<Connection> {
public Collection<MessageId> getMessageIds(Connection txn, GroupId g, public Collection<MessageId> getMessageIds(Connection txn, GroupId g,
Metadata query) throws DbException { Metadata query) throws DbException {
// If there are no query terms, return all delivered messages // If there are no query terms, return all delivered messages
if (query.isEmpty()) return getMessageIds(txn, g, DELIVERED); if (query.isEmpty()) return getMessageIds(txn, g);
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -1703,10 +1697,11 @@ abstract class JdbcDatabase implements Database<Connection> {
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT messageId, txCount > 0, seen FROM statuses" String sql = "SELECT messageId, txCount > 0, seen FROM statuses"
+ " WHERE groupId = ? AND contactId = ?"; + " WHERE groupId = ? AND contactId = ? AND state = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
ps.setInt(3, DELIVERED.getValue());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageStatus> statuses = new ArrayList<>(); List<MessageStatus> statuses = new ArrayList<>();
while (rs.next()) { while (rs.next()) {
@@ -1726,24 +1721,29 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
@Nullable
public MessageStatus getMessageStatus(Connection txn, ContactId c, public MessageStatus getMessageStatus(Connection txn, ContactId c,
MessageId m) throws DbException { MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT txCount > 0, seen FROM statuses" String sql = "SELECT txCount > 0, seen FROM statuses"
+ " WHERE messageId = ? AND contactId = ?"; + " WHERE messageId = ? AND contactId = ? AND state = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
ps.setInt(3, DELIVERED.getValue());
rs = ps.executeQuery(); rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException(); MessageStatus status = null;
boolean sent = rs.getBoolean(1); if (rs.next()) {
boolean seen = rs.getBoolean(2); boolean sent = rs.getBoolean(1);
boolean seen = rs.getBoolean(2);
status = new MessageStatus(m, c, sent, seen);
}
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
rs.close(); rs.close();
ps.close(); ps.close();
return new MessageStatus(m, c, sent, seen); return status;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs);
tryToClose(ps); tryToClose(ps);
@@ -2141,7 +2141,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " base, bitmap" + " base, bitmap"
+ " FROM incomingKeys" + " FROM incomingKeys"
+ " WHERE transportId = ?" + " WHERE transportId = ?"
+ " ORDER BY keySetId, rotationPeriod"; + " ORDER BY keySetId, periodOffset";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, t.getString()); ps.setString(1, t.getString());
rs = ps.executeQuery(); rs = ps.executeQuery();
@@ -2563,7 +2563,14 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
// Remove status rows for the messages in the group // Remove status rows for the messages in the group
for (MessageId m : getMessageIds(txn, g)) removeStatus(txn, c, m); sql = "DELETE FROM statuses"
+ " WHERE contactId = ? AND groupId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setBytes(2, g.getBytes());
affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
@@ -2647,24 +2654,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
private void removeStatus(Connection txn, ContactId c, MessageId m)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "DELETE FROM statuses"
+ " WHERE messageId = ? AND contactId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
@Override @Override
public void removeTransport(Connection txn, TransportId t) public void removeTransport(Connection txn, TransportId t)
throws DbException { throws DbException {
@@ -2944,12 +2933,69 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void updateTransportKeys(Connection txn, Collection<KeySet> keys) public void updateTransportKeys(Connection txn, KeySet ks)
throws DbException { throws DbException {
for (KeySet ks : keys) { PreparedStatement ps = null;
try {
// Update the outgoing keys
String sql = "UPDATE outgoingKeys SET rotationPeriod = ?,"
+ " tagKey = ?, headerKey = ?, stream = ?"
+ " WHERE transportId = ? AND keySetId = ?";
ps = txn.prepareStatement(sql);
TransportKeys k = ks.getTransportKeys(); TransportKeys k = ks.getTransportKeys();
removeTransportKeys(txn, k.getTransportId(), ks.getKeySetId()); OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
addTransportKeys(txn, ks.getContactId(), k); ps.setLong(1, outCurr.getRotationPeriod());
ps.setBytes(2, outCurr.getTagKey().getBytes());
ps.setBytes(3, outCurr.getHeaderKey().getBytes());
ps.setLong(4, outCurr.getStreamCounter());
ps.setString(5, k.getTransportId().getString());
ps.setInt(6, ks.getKeySetId().getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
// Update the incoming keys
sql = "UPDATE incomingKeys SET rotationPeriod = ?,"
+ " tagKey = ?, headerKey = ?, base = ?, bitmap = ?"
+ " WHERE transportId = ? AND keySetId = ?"
+ " AND periodOffset = ?";
ps = txn.prepareStatement(sql);
ps.setString(6, k.getTransportId().getString());
ps.setInt(7, ks.getKeySetId().getInt());
// Previous rotation period
IncomingKeys inPrev = k.getPreviousIncomingKeys();
ps.setLong(1, inPrev.getRotationPeriod());
ps.setBytes(2, inPrev.getTagKey().getBytes());
ps.setBytes(3, inPrev.getHeaderKey().getBytes());
ps.setLong(4, inPrev.getWindowBase());
ps.setBytes(5, inPrev.getWindowBitmap());
ps.setInt(8, OFFSET_PREV);
ps.addBatch();
// Current rotation period
IncomingKeys inCurr = k.getCurrentIncomingKeys();
ps.setLong(1, inCurr.getRotationPeriod());
ps.setBytes(2, inCurr.getTagKey().getBytes());
ps.setBytes(3, inCurr.getHeaderKey().getBytes());
ps.setLong(4, inCurr.getWindowBase());
ps.setBytes(5, inCurr.getWindowBitmap());
ps.setInt(8, OFFSET_CURR);
ps.addBatch();
// Next rotation period
IncomingKeys inNext = k.getNextIncomingKeys();
ps.setLong(1, inNext.getRotationPeriod());
ps.setBytes(2, inNext.getTagKey().getBytes());
ps.setBytes(3, inNext.getHeaderKey().getBytes());
ps.setLong(4, inNext.getWindowBase());
ps.setBytes(5, inNext.getWindowBitmap());
ps.setInt(8, OFFSET_NEXT);
ps.addBatch();
int[] batchAffected = ps.executeBatch();
if (batchAffected.length != 3) throw new DbStateException();
for (int rows : batchAffected)
if (rows < 0 || rows > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
} }
} }
} }

View File

@@ -13,6 +13,8 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -44,6 +46,8 @@ class KeyAgreementConnector {
private final KeyAgreementCrypto keyAgreementCrypto; private final KeyAgreementCrypto keyAgreementCrypto;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final ConnectionChooser connectionChooser; private final ConnectionChooser connectionChooser;
private final RecordReaderFactory recordReaderFactory;
private final RecordWriterFactory recordWriterFactory;
private final List<KeyAgreementListener> listeners = private final List<KeyAgreementListener> listeners =
new CopyOnWriteArrayList<>(); new CopyOnWriteArrayList<>();
@@ -54,11 +58,15 @@ class KeyAgreementConnector {
KeyAgreementConnector(Callbacks callbacks, KeyAgreementConnector(Callbacks callbacks,
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager, KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
ConnectionChooser connectionChooser) { ConnectionChooser connectionChooser,
RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory) {
this.callbacks = callbacks; this.callbacks = callbacks;
this.keyAgreementCrypto = keyAgreementCrypto; this.keyAgreementCrypto = keyAgreementCrypto;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.connectionChooser = connectionChooser; this.connectionChooser = connectionChooser;
this.recordReaderFactory = recordReaderFactory;
this.recordWriterFactory = recordWriterFactory;
} }
Payload listen(KeyPair localKeyPair) { Payload listen(KeyPair localKeyPair) {
@@ -119,7 +127,8 @@ class KeyAgreementConnector {
KeyAgreementConnection chosen = KeyAgreementConnection chosen =
connectionChooser.poll(CONNECTION_TIMEOUT); connectionChooser.poll(CONNECTION_TIMEOUT);
if (chosen == null) return null; if (chosen == null) return null;
return new KeyAgreementTransport(chosen); return new KeyAgreementTransport(recordReaderFactory,
recordWriterFactory, chosen);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.info("Interrupted while waiting for connection"); LOG.info("Interrupted while waiting for connection");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();

View File

@@ -14,10 +14,13 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -48,14 +51,17 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
KeyAgreementTaskImpl(CryptoComponent crypto, KeyAgreementTaskImpl(CryptoComponent crypto,
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus, KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
PayloadEncoder payloadEncoder, PluginManager pluginManager, PayloadEncoder payloadEncoder, PluginManager pluginManager,
ConnectionChooser connectionChooser) { ConnectionChooser connectionChooser,
RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory) {
this.crypto = crypto; this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto; this.keyAgreementCrypto = keyAgreementCrypto;
this.eventBus = eventBus; this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder; this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair(); localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, keyAgreementCrypto, connector = new KeyAgreementConnector(this, keyAgreementCrypto,
pluginManager, connectionChooser); pluginManager, connectionChooser, recordReaderFactory,
recordWriterFactory);
} }
@Override @Override
@@ -71,6 +77,7 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
if (localPayload != null) { if (localPayload != null) {
if (remotePayload == null) connector.stopListening(); if (remotePayload == null) connector.stopListening();
else interrupt(); else interrupt();
eventBus.broadcast(new KeyAgreementStoppedListeningEvent());
} }
} }

View File

@@ -4,9 +4,12 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
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.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@@ -14,8 +17,6 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_PAYLOAD_LENGTH_OFFSET;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT; import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM; import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY; import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
@@ -30,14 +31,17 @@ class KeyAgreementTransport {
Logger.getLogger(KeyAgreementTransport.class.getName()); Logger.getLogger(KeyAgreementTransport.class.getName());
private final KeyAgreementConnection kac; private final KeyAgreementConnection kac;
private final InputStream in; private final RecordReader reader;
private final OutputStream out; private final RecordWriter writer;
KeyAgreementTransport(KeyAgreementConnection kac) KeyAgreementTransport(RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory, KeyAgreementConnection kac)
throws IOException { throws IOException {
this.kac = kac; this.kac = kac;
in = kac.getConnection().getReader().getInputStream(); InputStream in = kac.getConnection().getReader().getInputStream();
out = kac.getConnection().getWriter().getOutputStream(); reader = recordReaderFactory.createRecordReader(in);
OutputStream out = kac.getConnection().getWriter().getOutputStream();
writer = recordWriterFactory.createRecordWriter(out);
} }
public DuplexTransportConnection getConnection() { public DuplexTransportConnection getConnection() {
@@ -74,9 +78,8 @@ class KeyAgreementTransport {
tryToClose(exception); tryToClose(exception);
} }
public void tryToClose(boolean exception) { private void tryToClose(boolean exception) {
try { try {
LOG.info("Closing connection");
kac.getConnection().getReader().dispose(exception, true); kac.getConnection().getReader().dispose(exception, true);
kac.getConnection().getWriter().dispose(exception); kac.getConnection().getWriter().dispose(exception);
} catch (IOException e) { } catch (IOException e) {
@@ -85,59 +88,27 @@ class KeyAgreementTransport {
} }
private void writeRecord(byte type, byte[] payload) throws IOException { private void writeRecord(byte type, byte[] payload) throws IOException {
byte[] recordHeader = new byte[RECORD_HEADER_LENGTH]; writer.writeRecord(new Record(PROTOCOL_VERSION, type, payload));
recordHeader[0] = PROTOCOL_VERSION; writer.flush();
recordHeader[1] = type;
ByteUtils.writeUint16(payload.length, recordHeader,
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
out.write(recordHeader);
out.write(payload);
out.flush();
} }
private byte[] readRecord(byte expectedType) throws AbortException { private byte[] readRecord(byte expectedType) throws AbortException {
while (true) { while (true) {
byte[] header = readHeader();
byte version = header[0], type = header[1];
int len = ByteUtils.readUint16(header,
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
// Reject unrecognised protocol version
if (version != PROTOCOL_VERSION) throw new AbortException(false);
if (type == ABORT) throw new AbortException(true);
if (type == expectedType) {
try {
return readData(len);
} catch (IOException e) {
throw new AbortException(e);
}
}
// Reject recognised but unexpected record type
if (type == KEY || type == CONFIRM) throw new AbortException(false);
// Skip unrecognised record type
try { try {
readData(len); Record record = reader.readRecord();
// Reject unrecognised protocol version
if (record.getProtocolVersion() != PROTOCOL_VERSION)
throw new AbortException(false);
byte type = record.getRecordType();
if (type == ABORT) throw new AbortException(true);
if (type == expectedType) return record.getPayload();
// Reject recognised but unexpected record type
if (type == KEY || type == CONFIRM)
throw new AbortException(false);
// Skip unrecognised record type
} catch (IOException e) { } catch (IOException e) {
throw new AbortException(e); throw new AbortException(e);
} }
} }
} }
private byte[] readHeader() throws AbortException {
try {
return readData(RECORD_HEADER_LENGTH);
} catch (IOException e) {
throw new AbortException(e);
}
}
private byte[] readData(int len) throws IOException {
byte[] data = new byte[len];
int offset = 0;
while (offset < data.length) {
int read = in.read(data, offset, data.length - offset);
if (read == -1) throw new EOFException();
offset += read;
}
return data;
}
} }

View File

@@ -24,7 +24,9 @@ import org.briarproject.bramble.api.system.Scheduler;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@@ -53,6 +55,7 @@ class Poller implements EventListener {
private final Clock clock; private final Clock clock;
private final Lock lock; private final Lock lock;
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
private final Set<TransportId> polling; // Locking: lock
@Inject @Inject
Poller(@IoExecutor Executor ioExecutor, Poller(@IoExecutor Executor ioExecutor,
@@ -69,16 +72,19 @@ class Poller implements EventListener {
this.clock = clock; this.clock = clock;
lock = new ReentrantLock(); lock = new ReentrantLock();
tasks = new HashMap<>(); tasks = new HashMap<>();
polling = new HashSet<>();
} }
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof ContactStatusChangedEvent) { if (e instanceof ContactStatusChangedEvent) {
ContactStatusChangedEvent c = (ContactStatusChangedEvent) e; ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
/*
if (c.isActive()) { if (c.isActive()) {
// Connect to the newly activated contact // Connect to the newly activated contact
connectToContact(c.getContactId()); connectToContact(c.getContactId());
} }
*/
} else if (e instanceof ConnectionClosedEvent) { } else if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e; ConnectionClosedEvent c = (ConnectionClosedEvent) e;
// Reschedule polling, the polling interval may have decreased // Reschedule polling, the polling interval may have decreased
@@ -215,20 +221,33 @@ class Poller implements EventListener {
@Override @Override
@IoExecutor @IoExecutor
public void run() { public void run() {
TransportId t = plugin.getId();
boolean shouldPoll;
lock.lock(); lock.lock();
try { try {
TransportId t = plugin.getId();
ScheduledPollTask scheduled = tasks.get(t); ScheduledPollTask scheduled = tasks.get(t);
if (scheduled != null && scheduled.task != this) if (scheduled != null && scheduled.task != this)
return; // Replaced by another task return; // Replaced by another task
tasks.remove(t); tasks.remove(t);
// Don't poll again if last poll is still running
shouldPoll = polling.add(t);
} finally { } finally {
lock.unlock(); lock.unlock();
} }
int delay = plugin.getPollingInterval(); int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble()); if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false); schedule(plugin, delay, false);
poll(plugin); if (shouldPoll) {
poll(plugin);
} else if (LOG.isLoggable(INFO)) {
LOG.info("Last poll for " + t + " is still running");
}
lock.lock();
try {
polling.remove(t);
} finally {
lock.unlock();
}
} }
} }
} }

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
interface BluetoothConnectionLimiter {
/**
* Informs the limiter that key agreement has started.
*/
void keyAgreementStarted();
/**
* Informs the limiter that key agreement has ended.
*/
void keyAgreementEnded();
/**
* Returns true if a contact connection can be opened. This method does not
* need to be called for key agreement connections.
*/
boolean canOpenContactConnection();
/**
* Informs the limiter that a contact connection has been opened. The
* limiter may close the new connection if key agreement is in progress.
* <p/>
* Returns false if the limiter has closed the new connection.
*/
boolean contactConnectionOpened(DuplexTransportConnection conn);
/**
* Informs the limiter that a key agreement connection has been opened.
*/
void keyAgreementConnectionOpened(DuplexTransportConnection conn);
/**
* Informs the limiter that the given connection has been closed.
*/
void connectionClosed(DuplexTransportConnection conn);
/**
* Informs the limiter that all connections have been closed.
*/
void allConnectionsClosed();
}

View File

@@ -0,0 +1,115 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@NotNullByDefault
@ThreadSafe
class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
private static final Logger LOG =
Logger.getLogger(BluetoothConnectionLimiterImpl.class.getName());
private final Object lock = new Object();
// The following are locking: lock
private final LinkedList<DuplexTransportConnection> connections =
new LinkedList<>();
private boolean keyAgreementInProgress = false;
@Override
public void keyAgreementStarted() {
List<DuplexTransportConnection> close;
synchronized (lock) {
keyAgreementInProgress = true;
close = new ArrayList<>(connections);
connections.clear();
}
if (LOG.isLoggable(INFO)) {
LOG.info("Key agreement started, closing " + close.size() +
" connections");
}
for (DuplexTransportConnection conn : close) tryToClose(conn);
}
@Override
public void keyAgreementEnded() {
synchronized (lock) {
keyAgreementInProgress = false;
}
LOG.info("Key agreement ended");
}
@Override
public boolean canOpenContactConnection() {
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Can't open contact connection during key agreement");
return false;
} else {
LOG.info("Can open contact connection");
return true;
}
}
}
@Override
public boolean contactConnectionOpened(DuplexTransportConnection conn) {
boolean accept = true;
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Refusing contact connection during key agreement");
accept = false;
} else {
LOG.info("Accepting contact connection");
connections.add(conn);
}
}
if (!accept) tryToClose(conn);
return accept;
}
@Override
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
synchronized (lock) {
LOG.info("Accepting key agreement connection");
connections.add(conn);
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getWriter().dispose(false);
conn.getReader().dispose(false, false);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public void connectionClosed(DuplexTransportConnection conn) {
synchronized (lock) {
connections.remove(conn);
if (LOG.isLoggable(INFO))
LOG.info("Connection closed, " + connections.size() + " open");
}
}
@Override
public void allConnectionsClosed() {
synchronized (lock) {
connections.clear();
LOG.info("All connections closed");
}
}
}

View File

@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
@@ -24,9 +26,11 @@ import org.briarproject.bramble.util.StringUtils;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -51,6 +55,14 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName()); Logger.getLogger(BluetoothPlugin.class.getName());
/**
* How many milliseconds to pause between connection attempts when
* polling, to avoid interfering with other Bluetooth or wifi connections.
*/
private static final int POLLING_PAUSE_MS = 3000;
final BluetoothConnectionLimiter connectionLimiter;
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final Backoff backoff; private final Backoff backoff;
@@ -91,8 +103,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
abstract DuplexTransportConnection connectTo(String address, String uuid) abstract DuplexTransportConnection connectTo(String address, String uuid)
throws IOException; throws IOException;
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) { Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
this.connectionLimiter = connectionLimiter;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.backoff = backoff; this.backoff = backoff;
@@ -110,6 +124,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
void onAdapterDisabled() { void onAdapterDisabled() {
LOG.info("Bluetooth disabled"); LOG.info("Bluetooth disabled");
tryToClose(socket); tryToClose(socket);
connectionLimiter.allConnectionsClosed();
callback.transportDisabled(); callback.transportDisabled();
} }
@@ -213,7 +228,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
return; return;
} }
backoff.reset(); backoff.reset();
callback.incomingConnectionCreated(conn); if (connectionLimiter.contactConnectionOpened(conn))
callback.incomingConnectionCreated(conn);
if (!running) return; if (!running) return;
} }
} }
@@ -245,25 +261,42 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
public void poll(Collection<ContactId> connected) { public void poll(Collection<ContactId> connected) {
if (!isRunning() || !shouldAllowContactConnections()) return; if (!isRunning() || !shouldAllowContactConnections()) return;
backoff.increment(); backoff.increment();
// Try to connect to known devices in parallel // Try to connect to known devices in a random order
Map<ContactId, TransportProperties> remote = Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties(); callback.getRemoteProperties();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) { List<ContactId> keys = new ArrayList<>(remote.keySet());
ContactId c = e.getKey(); Collections.shuffle(keys);
if (connected.contains(c)) continue; ioExecutor.execute(() -> {
String address = e.getValue().get(PROP_ADDRESS); boolean first = true;
if (StringUtils.isNullOrEmpty(address)) continue; for (ContactId c : keys) {
String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return; if (!isRunning() || !shouldAllowContactConnections()) return;
if (!connectionLimiter.canOpenContactConnection()) return;
if (connected.contains(c)) continue;
TransportProperties p = remote.get(c);
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
// Pause between connection attempts
if (first) {
first = false;
} else {
try {
Thread.sleep(POLLING_PAUSE_MS);
} catch (InterruptedException ex) {
LOG.info("Interrupted while polling");
Thread.currentThread().interrupt();
return;
}
}
DuplexTransportConnection conn = connect(address, uuid); DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) { if (conn != null) {
backoff.reset(); backoff.reset();
callback.outgoingConnectionCreated(c, conn); if (connectionLimiter.contactConnectionOpened(conn))
callback.outgoingConnectionCreated(c, conn);
} }
}); }
} });
} }
@Nullable @Nullable
@@ -300,12 +333,16 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public DuplexTransportConnection createConnection(ContactId c) { public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning() || !shouldAllowContactConnections()) return null; if (!isRunning() || !shouldAllowContactConnections()) return null;
if (!connectionLimiter.canOpenContactConnection()) return null;
TransportProperties p = callback.getRemoteProperties(c); TransportProperties p = callback.getRemoteProperties(c);
String address = p.get(PROP_ADDRESS); String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null; if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID); String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null; if (StringUtils.isNullOrEmpty(uuid)) return null;
return connect(address, uuid); DuplexTransportConnection conn = connect(address, uuid);
if (conn == null) return null;
// TODO: Why don't we reset the backoff here?
return connectionLimiter.contactConnectionOpened(conn) ? conn : null;
} }
@Override @Override
@@ -355,7 +392,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = UUID.nameUUIDFromBytes(commitment).toString(); String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid); LOG.info("Connecting to key agreement UUID " + uuid);
return connect(address, uuid); DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
return conn;
} }
private String parseAddress(BdfList descriptor) throws FormatException { private String parseAddress(BdfList descriptor) throws FormatException {
@@ -376,6 +415,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e; SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString())) if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(this::onSettingsUpdated); ioExecutor.execute(this::onSettingsUpdated);
} else if (e instanceof KeyAgreementListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
} }
} }
@@ -408,6 +451,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
public KeyAgreementConnection accept() throws IOException { public KeyAgreementConnection accept() throws IOException {
DuplexTransportConnection conn = acceptConnection(ss); DuplexTransportConnection conn = acceptConnection(ss);
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection"); if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
connectionLimiter.keyAgreementConnectionOpened(conn);
return new KeyAgreementConnection(conn, ID); return new KeyAgreementConnection(conn, ID);
} }

View File

@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -15,6 +16,8 @@ import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MINOR_VERSION;
@Module @Module
public class PropertiesModule { public class PropertiesModule {
@@ -33,7 +36,8 @@ public class PropertiesModule {
Clock clock) { Clock clock) {
TransportPropertyValidator validator = new TransportPropertyValidator( TransportPropertyValidator validator = new TransportPropertyValidator(
clientHelper, metadataEncoder, clock); clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID, validator); validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
validator);
return validator; return validator;
} }
@@ -42,11 +46,14 @@ public class PropertiesModule {
TransportPropertyManager getTransportPropertyManager( TransportPropertyManager getTransportPropertyManager(
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
ValidationManager validationManager, ContactManager contactManager, ValidationManager validationManager, ContactManager contactManager,
ClientVersioningManager clientVersioningManager,
TransportPropertyManagerImpl transportPropertyManager) { TransportPropertyManagerImpl transportPropertyManager) {
lifecycleManager.registerClient(transportPropertyManager); lifecycleManager.registerClient(transportPropertyManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
transportPropertyManager); transportPropertyManager);
contactManager.registerContactHook(transportPropertyManager); contactManager.registerContactHook(transportPropertyManager);
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
MINOR_VERSION, transportPropertyManager);
return transportPropertyManager; return transportPropertyManager;
} }
} }

View File

@@ -19,12 +19,15 @@ 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.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.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException; import org.briarproject.bramble.api.sync.InvalidMessageException;
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.bramble.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -34,15 +37,14 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class TransportPropertyManagerImpl implements TransportPropertyManager, class TransportPropertyManagerImpl implements TransportPropertyManager,
Client, ContactHook, IncomingMessageHook { Client, ContactHook, ClientVersioningHook, IncomingMessageHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final ClientVersioningManager clientVersioningManager;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
private final Clock clock; private final Clock clock;
@@ -50,22 +52,25 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Inject @Inject
TransportPropertyManagerImpl(DatabaseComponent db, TransportPropertyManagerImpl(DatabaseComponent db,
ClientHelper clientHelper, MetadataParser metadataParser, ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser,
ContactGroupFactory contactGroupFactory, Clock clock) { ContactGroupFactory contactGroupFactory, Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.clientVersioningManager = clientVersioningManager;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
this.clock = clock; this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID, localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
CLIENT_VERSION); MAJOR_VERSION);
} }
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void createLocalState(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);
// Ensure we've set things up for any pre-existing contacts // Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c); for (Contact c : db.getContacts(txn)) addingContact(txn, c);
} }
@@ -73,11 +78,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
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); Group g = getContactGroup(c);
// Return if we've already set things up for this contact
if (db.containsGroup(txn, g.getId())) return;
// Store the group and share it with the contact
db.addGroup(txn, g); db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); // Apply the client's visibility to the contact group
Visibility client = clientVersioningManager.getClientVisibility(txn,
c.getId(), CLIENT_ID, MAJOR_VERSION);
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
// Copy the latest local properties into the group // Copy the latest local properties into the group
Map<TransportId, TransportProperties> local = getLocalProperties(txn); Map<TransportId, TransportProperties> local = getLocalProperties(txn);
for (Entry<TransportId, TransportProperties> e : local.entrySet()) { for (Entry<TransportId, TransportProperties> e : local.entrySet()) {
@@ -91,6 +96,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
db.removeGroup(txn, getContactGroup(c)); db.removeGroup(txn, getContactGroup(c));
} }
@Override
public void onClientVisibilityChanging(Transaction txn, Contact c,
Visibility v) throws DbException {
// Apply the client's visibility to the contact group
Group g = getContactGroup(c);
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
}
@Override @Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta) public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
throws DbException, InvalidMessageException { throws DbException, InvalidMessageException {
@@ -289,7 +302,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private Group getContactGroup(Contact c) { private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID, return contactGroupFactory.createContactGroup(CLIENT_ID,
CLIENT_VERSION, c); MAJOR_VERSION, c);
} }
private void storeMessage(Transaction txn, GroupId g, TransportId t, private void storeMessage(Transaction txn, GroupId g, TransportId t,

View File

@@ -0,0 +1,21 @@
package org.briarproject.bramble.record;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import dagger.Module;
import dagger.Provides;
@Module
public class RecordModule {
@Provides
RecordReaderFactory provideRecordReaderFactory() {
return new RecordReaderFactoryImpl();
}
@Provides
RecordWriterFactory provideRecordWriterFactory() {
return new RecordWriterFactoryImpl();
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.bramble.record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import java.io.InputStream;
class RecordReaderFactoryImpl implements RecordReaderFactory {
@Override
public RecordReader createRecordReader(InputStream in) {
return new RecordReaderImpl(in);
}
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.bramble.record;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.util.ByteUtils;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
@NotThreadSafe
@NotNullByDefault
class RecordReaderImpl implements RecordReader {
private final DataInputStream in;
private final byte[] header = new byte[RECORD_HEADER_BYTES];
RecordReaderImpl(InputStream in) {
this.in = new DataInputStream(in);
}
@Override
public Record readRecord() throws IOException {
in.readFully(header);
byte protocolVersion = header[0];
byte recordType = header[1];
int payloadLength = ByteUtils.readUint16(header, 2);
if (payloadLength < 0 || payloadLength > MAX_RECORD_PAYLOAD_BYTES)
throw new FormatException();
byte[] payload = new byte[payloadLength];
in.readFully(payload);
return new Record(protocolVersion, recordType, payload);
}
@Override
public void close() throws IOException {
in.close();
}
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.bramble.record;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import java.io.OutputStream;
class RecordWriterFactoryImpl implements RecordWriterFactory {
@Override
public RecordWriter createRecordWriter(OutputStream out) {
return new RecordWriterImpl(out);
}
}

View File

@@ -0,0 +1,45 @@
package org.briarproject.bramble.record;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.util.ByteUtils;
import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
@NotThreadSafe
@NotNullByDefault
class RecordWriterImpl implements RecordWriter {
private final OutputStream out;
private final byte[] header = new byte[RECORD_HEADER_BYTES];
RecordWriterImpl(OutputStream out) {
this.out = out;
}
@Override
public void writeRecord(Record r) throws IOException {
byte[] payload = r.getPayload();
header[0] = r.getProtocolVersion();
header[1] = r.getRecordType();
ByteUtils.writeUint16(payload.length, header, 2);
out.write(header);
out.write(payload);
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void close() throws IOException {
out.close();
}
}

View File

@@ -14,8 +14,8 @@ 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.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent; import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent; import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
@@ -39,8 +39,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
/** /**
* An outgoing {@link SyncSession} suitable for duplex transports. The session * An outgoing {@link SyncSession} suitable for duplex transports. The session
@@ -67,7 +67,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final Clock clock; private final Clock clock;
private final ContactId contactId; private final ContactId contactId;
private final int maxLatency, maxIdleTime; private final int maxLatency, maxIdleTime;
private final RecordWriter recordWriter; private final SyncRecordWriter recordWriter;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks; private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false); private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
@@ -81,7 +81,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency, EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
int maxIdleTime, RecordWriter recordWriter) { int maxIdleTime, SyncRecordWriter recordWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -273,7 +273,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
b = db.generateRequestedBatch(txn, contactId, b = db.generateRequestedBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency); MAX_RECORD_PAYLOAD_BYTES, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId)); setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {

View File

@@ -20,6 +20,9 @@ import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@NotNullByDefault @NotNullByDefault
class GroupFactoryImpl implements GroupFactory { class GroupFactoryImpl implements GroupFactory {
private static final byte[] FORMAT_VERSION_BYTES =
new byte[] {FORMAT_VERSION};
private final CryptoComponent crypto; private final CryptoComponent crypto;
@Inject @Inject
@@ -28,12 +31,12 @@ class GroupFactoryImpl implements GroupFactory {
} }
@Override @Override
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) { public Group createGroup(ClientId c, int majorVersion, byte[] descriptor) {
byte[] clientVersionBytes = new byte[INT_32_BYTES]; byte[] majorVersionBytes = new byte[INT_32_BYTES];
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0); ByteUtils.writeUint32(majorVersion, majorVersionBytes, 0);
byte[] hash = crypto.hash(LABEL, new byte[] {FORMAT_VERSION}, byte[] hash = crypto.hash(LABEL, FORMAT_VERSION_BYTES,
StringUtils.toUtf8(c.getString()), clientVersionBytes, StringUtils.toUtf8(c.getString()), majorVersionBytes,
descriptor); descriptor);
return new Group(new GroupId(hash), c, descriptor); return new Group(new GroupId(hash), c, majorVersion, descriptor);
} }
} }

View File

@@ -16,8 +16,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import java.io.IOException; import java.io.IOException;
@@ -43,13 +43,13 @@ class IncomingSession implements SyncSession, EventListener {
private final Executor dbExecutor; private final Executor dbExecutor;
private final EventBus eventBus; private final EventBus eventBus;
private final ContactId contactId; private final ContactId contactId;
private final RecordReader recordReader; private final SyncRecordReader recordReader;
private volatile boolean interrupted = false; private volatile boolean interrupted = false;
IncomingSession(DatabaseComponent db, Executor dbExecutor, IncomingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, EventBus eventBus, ContactId contactId,
RecordReader recordReader) { SyncRecordReader recordReader) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;

View File

@@ -16,6 +16,7 @@ import static org.briarproject.bramble.api.sync.Message.FORMAT_VERSION;
import static org.briarproject.bramble.api.sync.MessageId.BLOCK_LABEL; import static org.briarproject.bramble.api.sync.MessageId.BLOCK_LABEL;
import static org.briarproject.bramble.api.sync.MessageId.ID_LABEL; import static org.briarproject.bramble.api.sync.MessageId.ID_LABEL;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
@@ -23,6 +24,9 @@ import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
@NotNullByDefault @NotNullByDefault
class MessageFactoryImpl implements MessageFactory { class MessageFactoryImpl implements MessageFactory {
private static final byte[] FORMAT_VERSION_BYTES =
new byte[] {FORMAT_VERSION};
private final CryptoComponent crypto; private final CryptoComponent crypto;
@Inject @Inject
@@ -34,14 +38,7 @@ class MessageFactoryImpl implements MessageFactory {
public Message createMessage(GroupId g, long timestamp, byte[] body) { public Message createMessage(GroupId g, long timestamp, byte[] body) {
if (body.length > MAX_MESSAGE_BODY_LENGTH) if (body.length > MAX_MESSAGE_BODY_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
byte[] versionBytes = new byte[] {FORMAT_VERSION}; MessageId id = getMessageId(g, timestamp, body);
// There's only one block, so the root hash is the hash of the block
byte[] rootHash = crypto.hash(BLOCK_LABEL, versionBytes, body);
byte[] timeBytes = new byte[INT_64_BYTES];
ByteUtils.writeUint64(timestamp, timeBytes, 0);
byte[] idHash = crypto.hash(ID_LABEL, versionBytes, g.getBytes(),
timeBytes, rootHash);
MessageId id = new MessageId(idHash);
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length]; byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH); System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH); ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
@@ -49,10 +46,38 @@ class MessageFactoryImpl implements MessageFactory {
return new Message(id, g, timestamp, raw); return new Message(id, g, timestamp, raw);
} }
private MessageId getMessageId(GroupId g, long timestamp, byte[] body) {
// There's only one block, so the root hash is the hash of the block
byte[] rootHash = crypto.hash(BLOCK_LABEL, FORMAT_VERSION_BYTES, body);
byte[] timeBytes = new byte[INT_64_BYTES];
ByteUtils.writeUint64(timestamp, timeBytes, 0);
byte[] idHash = crypto.hash(ID_LABEL, FORMAT_VERSION_BYTES,
g.getBytes(), timeBytes, rootHash);
return new MessageId(idHash);
}
@Override
public Message createMessage(byte[] raw) {
if (raw.length < MESSAGE_HEADER_LENGTH)
throw new IllegalArgumentException();
if (raw.length > MAX_MESSAGE_LENGTH)
throw new IllegalArgumentException();
byte[] groupId = new byte[UniqueId.LENGTH];
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
GroupId g = new GroupId(groupId);
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
byte[] body = new byte[raw.length - MESSAGE_HEADER_LENGTH];
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
MessageId id = getMessageId(g, timestamp, body);
return new Message(id, g, timestamp, raw);
}
@Override @Override
public Message createMessage(MessageId m, byte[] raw) { public Message createMessage(MessageId m, byte[] raw) {
if (raw.length < MESSAGE_HEADER_LENGTH) if (raw.length < MESSAGE_HEADER_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (raw.length > MAX_MESSAGE_LENGTH)
throw new IllegalArgumentException();
byte[] groupId = new byte[UniqueId.LENGTH]; byte[] groupId = new byte[UniqueId.LENGTH];
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH); System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH); long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);

View File

@@ -1,28 +0,0 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.RecordReaderFactory;
import java.io.InputStream;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class RecordReaderFactoryImpl implements RecordReaderFactory {
private final MessageFactory messageFactory;
@Inject
RecordReaderFactoryImpl(MessageFactory messageFactory) {
this.messageFactory = messageFactory;
}
@Override
public RecordReader createRecordReader(InputStream in) {
return new RecordReaderImpl(messageFactory, in);
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.RecordWriterFactory;
import java.io.OutputStream;
@NotNullByDefault
class RecordWriterFactoryImpl implements RecordWriterFactory {
@Override
public RecordWriter createRecordWriter(OutputStream out) {
return new RecordWriterImpl(out);
}
}

View File

@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
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.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.RecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import java.io.IOException; import java.io.IOException;
@@ -29,8 +29,8 @@ import javax.annotation.concurrent.ThreadSafe;
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 org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
/** /**
* An outgoing {@link SyncSession} suitable for simplex transports. The session * An outgoing {@link SyncSession} suitable for simplex transports. The session
@@ -51,7 +51,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final EventBus eventBus; private final EventBus eventBus;
private final ContactId contactId; private final ContactId contactId;
private final int maxLatency; private final int maxLatency;
private final RecordWriter recordWriter; private final SyncRecordWriter recordWriter;
private final AtomicInteger outstandingQueries; private final AtomicInteger outstandingQueries;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks; private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
@@ -59,7 +59,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, EventBus eventBus, ContactId contactId,
int maxLatency, RecordWriter recordWriter) { int maxLatency, SyncRecordWriter recordWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -171,7 +171,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
b = db.generateBatch(txn, contactId, b = db.generateBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency); MAX_RECORD_PAYLOAD_BYTES, maxLatency);
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);

View File

@@ -9,8 +9,8 @@ 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.sync.GroupFactory; import org.briarproject.bramble.api.sync.GroupFactory;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.RecordReaderFactory; import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
import org.briarproject.bramble.api.sync.RecordWriterFactory; import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
@@ -52,22 +52,23 @@ public class SyncModule {
} }
@Provides @Provides
RecordReaderFactory provideRecordReaderFactory( SyncRecordReaderFactory provideRecordReaderFactory(
RecordReaderFactoryImpl recordReaderFactory) { SyncRecordReaderFactoryImpl recordReaderFactory) {
return recordReaderFactory; return recordReaderFactory;
} }
@Provides @Provides
RecordWriterFactory provideRecordWriterFactory() { SyncRecordWriterFactory provideRecordWriterFactory(
return new RecordWriterFactoryImpl(); SyncRecordWriterFactoryImpl recordWriterFactory) {
return recordWriterFactory;
} }
@Provides @Provides
@Singleton @Singleton
SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db, SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, EventBus eventBus, @DatabaseExecutor Executor dbExecutor, EventBus eventBus,
Clock clock, RecordReaderFactory recordReaderFactory, Clock clock, SyncRecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory) { SyncRecordWriterFactory recordWriterFactory) {
return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock, return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
recordReaderFactory, recordWriterFactory); recordReaderFactory, recordWriterFactory);
} }

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
import java.io.InputStream;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class SyncRecordReaderFactoryImpl implements SyncRecordReaderFactory {
private final MessageFactory messageFactory;
private final RecordReaderFactory recordReaderFactory;
@Inject
SyncRecordReaderFactoryImpl(MessageFactory messageFactory,
RecordReaderFactory recordReaderFactory) {
this.messageFactory = messageFactory;
this.recordReaderFactory = recordReaderFactory;
}
@Override
public SyncRecordReader createRecordReader(InputStream in) {
RecordReader reader = recordReaderFactory.createRecordReader(in);
return new SyncRecordReaderImpl(messageFactory, reader);
}
}

View File

@@ -3,82 +3,56 @@ package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
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.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK; import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE; import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER; import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST; import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class RecordReaderImpl implements RecordReader { class SyncRecordReaderImpl implements SyncRecordReader {
private enum State {BUFFER_EMPTY, BUFFER_FULL, EOF}
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final InputStream in; private final RecordReader reader;
private final byte[] header, payload;
private State state = State.BUFFER_EMPTY; @Nullable
private int payloadLength = 0; private Record nextRecord = null;
private boolean eof = false;
RecordReaderImpl(MessageFactory messageFactory, InputStream in) { SyncRecordReaderImpl(MessageFactory messageFactory, RecordReader reader) {
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
this.in = in; this.reader = reader;
header = new byte[RECORD_HEADER_LENGTH];
payload = new byte[MAX_RECORD_PAYLOAD_LENGTH];
} }
private void readRecord() throws IOException { private void readRecord() throws IOException {
if (state != State.BUFFER_EMPTY) throw new IllegalStateException(); assert nextRecord == null;
while (true) { while (true) {
// Read the header nextRecord = reader.readRecord();
int offset = 0;
while (offset < RECORD_HEADER_LENGTH) {
int read =
in.read(header, offset, RECORD_HEADER_LENGTH - offset);
if (read == -1) {
if (offset > 0) throw new FormatException();
state = State.EOF;
return;
}
offset += read;
}
byte version = header[0], type = header[1];
payloadLength = ByteUtils.readUint16(header, 2);
// Check the protocol version // Check the protocol version
byte version = nextRecord.getProtocolVersion();
if (version != PROTOCOL_VERSION) throw new FormatException(); if (version != PROTOCOL_VERSION) throw new FormatException();
// Check the payload length byte type = nextRecord.getRecordType();
if (payloadLength > MAX_RECORD_PAYLOAD_LENGTH)
throw new FormatException();
// Read the payload
offset = 0;
while (offset < payloadLength) {
int read = in.read(payload, offset, payloadLength - offset);
if (read == -1) throw new FormatException();
offset += read;
}
state = State.BUFFER_FULL;
// Return if this is a known record type, otherwise continue // Return if this is a known record type, otherwise continue
if (type == ACK || type == MESSAGE || type == OFFER || if (type == ACK || type == MESSAGE || type == OFFER ||
type == REQUEST) { type == REQUEST) {
@@ -87,6 +61,11 @@ class RecordReaderImpl implements RecordReader {
} }
} }
private byte getNextRecordType() {
assert nextRecord != null;
return nextRecord.getRecordType();
}
/** /**
* Returns true if there's another record available or false if we've * Returns true if there's another record available or false if we've
* reached the end of the input stream. * reached the end of the input stream.
@@ -97,14 +76,21 @@ class RecordReaderImpl implements RecordReader {
*/ */
@Override @Override
public boolean eof() throws IOException { public boolean eof() throws IOException {
if (state == State.BUFFER_EMPTY) readRecord(); if (nextRecord != null) return false;
if (state == State.BUFFER_EMPTY) throw new IllegalStateException(); if (eof) return true;
return state == State.EOF; try {
readRecord();
return false;
} catch (EOFException e) {
nextRecord = null;
eof = true;
return true;
}
} }
@Override @Override
public boolean hasAck() throws IOException { public boolean hasAck() throws IOException {
return !eof() && header[1] == ACK; return !eof() && getNextRecordType() == ACK;
} }
@Override @Override
@@ -114,45 +100,41 @@ class RecordReaderImpl implements RecordReader {
} }
private List<MessageId> readMessageIds() throws IOException { private List<MessageId> readMessageIds() throws IOException {
if (payloadLength == 0) throw new FormatException(); assert nextRecord != null;
if (payloadLength % UniqueId.LENGTH != 0) throw new FormatException(); byte[] payload = nextRecord.getPayload();
List<MessageId> ids = new ArrayList<>(); if (payload.length == 0) throw new FormatException();
for (int off = 0; off < payloadLength; off += UniqueId.LENGTH) { if (payload.length % UniqueId.LENGTH != 0) throw new FormatException();
List<MessageId> ids = new ArrayList<>(payload.length / UniqueId.LENGTH);
for (int off = 0; off < payload.length; off += UniqueId.LENGTH) {
byte[] id = new byte[UniqueId.LENGTH]; byte[] id = new byte[UniqueId.LENGTH];
System.arraycopy(payload, off, id, 0, UniqueId.LENGTH); System.arraycopy(payload, off, id, 0, UniqueId.LENGTH);
ids.add(new MessageId(id)); ids.add(new MessageId(id));
} }
state = State.BUFFER_EMPTY; nextRecord = null;
return ids; return ids;
} }
@Override @Override
public boolean hasMessage() throws IOException { public boolean hasMessage() throws IOException {
return !eof() && header[1] == MESSAGE; return !eof() && getNextRecordType() == MESSAGE;
} }
@Override @Override
public Message readMessage() throws IOException { public Message readMessage() throws IOException {
if (!hasMessage()) throw new FormatException(); if (!hasMessage()) throw new FormatException();
if (payloadLength <= MESSAGE_HEADER_LENGTH) throw new FormatException(); assert nextRecord != null;
// Group ID byte[] payload = nextRecord.getPayload();
byte[] id = new byte[UniqueId.LENGTH]; if (payload.length < MESSAGE_HEADER_LENGTH) throw new FormatException();
System.arraycopy(payload, 0, id, 0, UniqueId.LENGTH); // Validate timestamp
GroupId groupId = new GroupId(id);
// Timestamp
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH); long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
if (timestamp < 0) throw new FormatException(); if (timestamp < 0) throw new FormatException();
// Body nextRecord = null;
byte[] body = new byte[payloadLength - MESSAGE_HEADER_LENGTH]; return messageFactory.createMessage(payload);
System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
payloadLength - MESSAGE_HEADER_LENGTH);
state = State.BUFFER_EMPTY;
return messageFactory.createMessage(groupId, timestamp, body);
} }
@Override @Override
public boolean hasOffer() throws IOException { public boolean hasOffer() throws IOException {
return !eof() && header[1] == OFFER; return !eof() && getNextRecordType() == OFFER;
} }
@Override @Override
@@ -163,7 +145,7 @@ class RecordReaderImpl implements RecordReader {
@Override @Override
public boolean hasRequest() throws IOException { public boolean hasRequest() throws IOException {
return !eof() && header[1] == REQUEST; return !eof() && getNextRecordType() == REQUEST;
} }
@Override @Override

View File

@@ -0,0 +1,28 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import java.io.OutputStream;
import javax.inject.Inject;
@NotNullByDefault
class SyncRecordWriterFactoryImpl implements SyncRecordWriterFactory {
private final RecordWriterFactory recordWriterFactory;
@Inject
SyncRecordWriterFactoryImpl(RecordWriterFactory recordWriterFactory) {
this.recordWriterFactory = recordWriterFactory;
}
@Override
public SyncRecordWriter createRecordWriter(OutputStream out) {
RecordWriter writer = recordWriterFactory.createRecordWriter(out);
return new SyncRecordWriterImpl(writer);
}
}

View File

@@ -1,81 +1,67 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordTypes;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK; import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER; import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST; import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class RecordWriterImpl implements RecordWriter { class SyncRecordWriterImpl implements SyncRecordWriter {
private final OutputStream out; private final RecordWriter writer;
private final byte[] header; private final ByteArrayOutputStream payload = new ByteArrayOutputStream();
private final ByteArrayOutputStream payload;
RecordWriterImpl(OutputStream out) { SyncRecordWriterImpl(RecordWriter writer) {
this.out = out; this.writer = writer;
header = new byte[RECORD_HEADER_LENGTH];
header[0] = PROTOCOL_VERSION;
payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH);
} }
private void writeRecord(byte recordType) throws IOException { private void writeRecord(byte recordType) throws IOException {
header[1] = recordType; writer.writeRecord(new Record(PROTOCOL_VERSION, recordType,
ByteUtils.writeUint16(payload.size(), header, 2); payload.toByteArray()));
out.write(header);
payload.writeTo(out);
payload.reset(); payload.reset();
} }
@Override @Override
public void writeAck(Ack a) throws IOException { public void writeAck(Ack a) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : a.getMessageIds()) payload.write(m.getBytes()); for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
writeRecord(ACK); writeRecord(ACK);
} }
@Override @Override
public void writeMessage(byte[] raw) throws IOException { public void writeMessage(byte[] raw) throws IOException {
header[1] = RecordTypes.MESSAGE; writer.writeRecord(new Record(PROTOCOL_VERSION, MESSAGE, raw));
ByteUtils.writeUint16(raw.length, header, 2);
out.write(header);
out.write(raw);
} }
@Override @Override
public void writeOffer(Offer o) throws IOException { public void writeOffer(Offer o) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : o.getMessageIds()) payload.write(m.getBytes()); for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
writeRecord(OFFER); writeRecord(OFFER);
} }
@Override @Override
public void writeRequest(Request r) throws IOException { public void writeRequest(Request r) throws IOException {
if (payload.size() != 0) throw new IllegalStateException();
for (MessageId m : r.getMessageIds()) payload.write(m.getBytes()); for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
writeRecord(REQUEST); writeRecord(REQUEST);
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
out.flush(); writer.flush();
} }
} }

View File

@@ -5,10 +5,10 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.RecordReader; import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.RecordReaderFactory; import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
import org.briarproject.bramble.api.sync.RecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.RecordWriterFactory; import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import org.briarproject.bramble.api.sync.SyncSession; import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
@@ -28,14 +28,14 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
private final Executor dbExecutor; private final Executor dbExecutor;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock; private final Clock clock;
private final RecordReaderFactory recordReaderFactory; private final SyncRecordReaderFactory recordReaderFactory;
private final RecordWriterFactory recordWriterFactory; private final SyncRecordWriterFactory recordWriterFactory;
@Inject @Inject
SyncSessionFactoryImpl(DatabaseComponent db, SyncSessionFactoryImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, EventBus eventBus, @DatabaseExecutor Executor dbExecutor, EventBus eventBus,
Clock clock, RecordReaderFactory recordReaderFactory, Clock clock, SyncRecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory) { SyncRecordWriterFactory recordWriterFactory) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -46,14 +46,16 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
@Override @Override
public SyncSession createIncomingSession(ContactId c, InputStream in) { public SyncSession createIncomingSession(ContactId c, InputStream in) {
RecordReader recordReader = recordReaderFactory.createRecordReader(in); SyncRecordReader recordReader =
recordReaderFactory.createRecordReader(in);
return new IncomingSession(db, dbExecutor, eventBus, c, recordReader); return new IncomingSession(db, dbExecutor, eventBus, c, recordReader);
} }
@Override @Override
public SyncSession createSimplexOutgoingSession(ContactId c, public SyncSession createSimplexOutgoingSession(ContactId c,
int maxLatency, OutputStream out) { int maxLatency, OutputStream out) {
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out); SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
maxLatency, recordWriter); maxLatency, recordWriter);
} }
@@ -61,7 +63,8 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
@Override @Override
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency, public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, OutputStream out) { int maxIdleTime, OutputStream out) {
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out); SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
maxLatency, maxIdleTime, recordWriter); maxLatency, maxIdleTime, recordWriter);
} }

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
@@ -51,8 +52,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor, validationExecutor; private final Executor dbExecutor, validationExecutor;
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final Map<ClientId, MessageValidator> validators; private final Map<ClientMajorVersion, MessageValidator> validators;
private final Map<ClientId, IncomingMessageHook> hooks; private final Map<ClientMajorVersion, IncomingMessageHook> hooks;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@Inject @Inject
@@ -81,14 +82,15 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
@Override @Override
public void registerMessageValidator(ClientId c, MessageValidator v) { public void registerMessageValidator(ClientId c, int majorVersion,
validators.put(c, v); MessageValidator v) {
validators.put(new ClientMajorVersion(c, majorVersion), v);
} }
@Override @Override
public void registerIncomingMessageHook(ClientId c, public void registerIncomingMessageHook(ClientId c, int majorVersion,
IncomingMessageHook hook) { IncomingMessageHook hook) {
hooks.put(c, hook); hooks.put(new ClientMajorVersion(c, majorVersion), hook);
} }
private void validateOutstandingMessagesAsync() { private void validateOutstandingMessagesAsync() {
@@ -199,9 +201,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
Message m = messageFactory.createMessage(id, raw); Message m = messageFactory.createMessage(id, raw);
Group g = db.getGroup(txn, m.getGroupId()); Group g = db.getGroup(txn, m.getGroupId());
ClientId c = g.getClientId(); ClientId c = g.getClientId();
int majorVersion = g.getMajorVersion();
Metadata meta = Metadata meta =
db.getMessageMetadataForValidator(txn, id); db.getMessageMetadataForValidator(txn, id);
DeliveryResult result = deliverMessage(txn, m, c, meta); DeliveryResult result =
deliverMessage(txn, m, c, majorVersion, meta);
if (result.valid) { if (result.valid) {
pending.addAll(getPendingDependents(txn, id)); pending.addAll(getPendingDependents(txn, id));
if (result.share) { if (result.share) {
@@ -237,14 +241,16 @@ class ValidationManagerImpl implements ValidationManager, Service,
@ValidationExecutor @ValidationExecutor
private void validateMessage(Message m, Group g) { private void validateMessage(Message m, Group g) {
MessageValidator v = validators.get(g.getClientId()); ClientMajorVersion cv =
new ClientMajorVersion(g.getClientId(), g.getMajorVersion());
MessageValidator v = validators.get(cv);
if (v == null) { if (v == null) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING)) LOG.warning("No validator for " + cv);
LOG.warning("No validator for " + g.getClientId().getString());
} else { } else {
try { try {
MessageContext context = v.validateMessage(m, g); MessageContext context = v.validateMessage(m, g);
storeMessageContextAsync(m, g.getClientId(), context); storeMessageContextAsync(m, g.getClientId(),
g.getMajorVersion(), context);
} catch (InvalidMessageException e) { } catch (InvalidMessageException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e); LOG.log(INFO, e.toString(), e);
@@ -256,12 +262,13 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
private void storeMessageContextAsync(Message m, ClientId c, private void storeMessageContextAsync(Message m, ClientId c,
MessageContext result) { int majorVersion, MessageContext result) {
dbExecutor.execute(() -> storeMessageContext(m, c, result)); dbExecutor.execute(() ->
storeMessageContext(m, c, majorVersion, result));
} }
@DatabaseExecutor @DatabaseExecutor
private void storeMessageContext(Message m, ClientId c, private void storeMessageContext(Message m, ClientId c, int majorVersion,
MessageContext context) { MessageContext context) {
try { try {
MessageId id = m.getId(); MessageId id = m.getId();
@@ -292,7 +299,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
Metadata meta = context.getMetadata(); Metadata meta = context.getMetadata();
db.mergeMessageMetadata(txn, id, meta); db.mergeMessageMetadata(txn, id, meta);
if (allDelivered) { if (allDelivered) {
DeliveryResult result = deliverMessage(txn, m, c, meta); DeliveryResult result =
deliverMessage(txn, m, c, majorVersion, meta);
if (result.valid) { if (result.valid) {
pending = getPendingDependents(txn, id); pending = getPendingDependents(txn, id);
if (result.share) { if (result.share) {
@@ -324,10 +332,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
@DatabaseExecutor @DatabaseExecutor
private DeliveryResult deliverMessage(Transaction txn, Message m, private DeliveryResult deliverMessage(Transaction txn, Message m,
ClientId c, Metadata meta) throws DbException { ClientId c, int majorVersion, Metadata meta) throws DbException {
// Deliver the message to the client if it's registered a hook // Deliver the message to the client if it's registered a hook
boolean shareMsg = false; boolean shareMsg = false;
IncomingMessageHook hook = hooks.get(c); ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
IncomingMessageHook hook = hooks.get(cv);
if (hook != null) { if (hook != null) {
try { try {
shareMsg = hook.incomingMessage(txn, m, meta); shareMsg = hook.incomingMessage(txn, m, meta);

View File

@@ -0,0 +1,10 @@
package org.briarproject.bramble.versioning;
interface ClientVersioningConstants {
// Metadata keys
String MSG_KEY_UPDATE_VERSION = "version";
String MSG_KEY_LOCAL = "local";
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -0,0 +1,622 @@
package org.briarproject.bramble.versioning;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
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.Group;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@NotNullByDefault
class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
Service, ContactHook, IncomingMessageHook {
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final ContactGroupFactory contactGroupFactory;
private final Clock clock;
private final Group localGroup;
private final List<ClientVersion> clients = new CopyOnWriteArrayList<>();
private final Map<ClientMajorVersion, ClientVersioningHook> hooks =
new ConcurrentHashMap<>();
@Inject
ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
ContactGroupFactory contactGroupFactory, Clock clock) {
this.db = db;
this.clientHelper = clientHelper;
this.contactGroupFactory = contactGroupFactory;
this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
MAJOR_VERSION);
}
@Override
public void registerClient(ClientId clientId, int majorVersion,
int minorVersion, ClientVersioningHook hook) {
ClientMajorVersion cv = new ClientMajorVersion(clientId, majorVersion);
clients.add(new ClientVersion(cv, minorVersion));
hooks.put(cv, hook);
}
@Override
public Visibility getClientVisibility(Transaction txn, ContactId contactId,
ClientId clientId, int majorVersion) throws DbException {
try {
Contact contact = db.getContact(txn, contactId);
Group g = getContactGroup(contact);
// Contact may be in the process of being added or removed, so
// contact group may not exist
if (!db.containsGroup(txn, g.getId())) return INVISIBLE;
LatestUpdates latest = findLatestUpdates(txn, g.getId());
if (latest.local == null) throw new DbException();
if (latest.remote == null) return INVISIBLE;
Update localUpdate = loadUpdate(txn, latest.local.messageId);
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
Map<ClientMajorVersion, Visibility> visibilities =
getVisibilities(localUpdate.states, remoteUpdate.states);
ClientMajorVersion cv =
new ClientMajorVersion(clientId, majorVersion);
Visibility v = visibilities.get(cv);
return v == null ? INVISIBLE : v;
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public void createLocalState(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup);
// Set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@Override
public void startService() throws ServiceException {
List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions);
try {
Transaction txn = db.startTransaction(false);
try {
if (updateClientVersions(txn, versions)) {
for (Contact c : db.getContacts(txn))
clientVersionsUpdated(txn, c, versions);
}
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
}
} catch (DbException e) {
throw new ServiceException(e);
}
}
@Override
public void stopService() throws ServiceException {
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
// Create a group and share it with the contact
Group g = getContactGroup(c);
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
// Create and store the first local update
List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions);
storeFirstUpdate(txn, g.getId(), versions);
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
db.removeGroup(txn, getContactGroup(c));
}
@Override
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
throws DbException, InvalidMessageException {
try {
// Parse the new remote update
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
List<ClientState> newRemoteStates = newRemoteUpdate.states;
long newRemoteUpdateVersion = newRemoteUpdate.updateVersion;
// Find the latest local and remote updates, if any
LatestUpdates latest = findLatestUpdates(txn, m.getGroupId());
// If this update is obsolete, delete it and return
if (latest.remote != null
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId());
return false;
}
// Load and parse the latest local update
if (latest.local == null) throw new DbException();
Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId);
List<ClientState> oldLocalStates = oldLocalUpdate.states;
long oldLocalUpdateVersion = oldLocalUpdate.updateVersion;
// Load and parse the previous remote update, if any
List<ClientState> oldRemoteStates;
if (latest.remote == null) {
oldRemoteStates = emptyList();
} else {
oldRemoteStates =
loadUpdate(txn, latest.remote.messageId).states;
// Delete the previous remote update
db.deleteMessage(txn, latest.remote.messageId);
db.deleteMessageMetadata(txn, latest.remote.messageId);
}
// Update the local states from the remote states if necessary
List<ClientState> newLocalStates = updateStatesFromRemoteStates(
oldLocalStates, newRemoteStates);
if (!oldLocalStates.equals(newLocalStates)) {
// Delete the latest local update
db.deleteMessage(txn, latest.local.messageId);
db.deleteMessageMetadata(txn, latest.local.messageId);
// Store a new local update
storeUpdate(txn, m.getGroupId(), newLocalStates,
oldLocalUpdateVersion + 1);
}
// Calculate the old and new client visibilities
Map<ClientMajorVersion, Visibility> before =
getVisibilities(oldLocalStates, oldRemoteStates);
Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates);
// Call hooks for any visibilities that have changed
if (!before.equals(after)) {
Contact c = getContact(txn, m.getGroupId());
callVisibilityHooks(txn, c, before, after);
}
} catch (FormatException e) {
throw new InvalidMessageException(e);
}
return false;
}
private void storeClientVersions(Transaction txn,
List<ClientVersion> versions) throws DbException {
long now = clock.currentTimeMillis();
BdfList body = encodeClientVersions(versions);
try {
Message m = clientHelper.createMessage(localGroup.getId(), now,
body);
db.addLocalMessage(txn, m, new Metadata(), false);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
private BdfList encodeClientVersions(List<ClientVersion> versions) {
BdfList encoded = new BdfList();
for (ClientVersion cv : versions) encoded.add(encodeClientVersion(cv));
return encoded;
}
private BdfList encodeClientVersion(ClientVersion cv) {
return BdfList.of(cv.majorVersion.getClientId().getString(),
cv.majorVersion.getMajorVersion(), cv.minorVersion);
}
/**
* Stores the local client versions and returns true if an update needs to
* be sent to contacts.
*/
private boolean updateClientVersions(Transaction txn,
List<ClientVersion> newVersions) throws DbException {
Collection<MessageId> ids = db.getMessageIds(txn, localGroup.getId());
if (ids.isEmpty()) {
storeClientVersions(txn, newVersions);
return true;
}
if (ids.size() != 1) throw new DbException();
MessageId m = ids.iterator().next();
List<ClientVersion> oldVersions = loadClientVersions(txn, m);
if (oldVersions.equals(newVersions)) return false;
db.removeMessage(txn, m);
storeClientVersions(txn, newVersions);
return true;
}
private List<ClientVersion> loadClientVersions(Transaction txn,
MessageId m) throws DbException {
try {
BdfList body = clientHelper.getMessageAsList(txn, m);
if (body == null) throw new DbException();
return parseClientVersions(body);
} catch (FormatException e) {
throw new DbException(e);
}
}
private List<ClientVersion> parseClientVersions(BdfList body)
throws FormatException {
int size = body.size();
List<ClientVersion> parsed = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
BdfList cv = body.getList(i);
ClientId clientId = new ClientId(cv.getString(0));
int majorVersion = cv.getLong(1).intValue();
int minorVersion = cv.getLong(2).intValue();
parsed.add(new ClientVersion(clientId, majorVersion,
minorVersion));
}
return parsed;
}
private void clientVersionsUpdated(Transaction txn, Contact c,
List<ClientVersion> versions) throws DbException {
try {
// Find the latest local and remote updates
Group g = getContactGroup(c);
LatestUpdates latest = findLatestUpdates(txn, g.getId());
// Load and parse the latest local update
if (latest.local == null) throw new DbException();
Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId);
List<ClientState> oldLocalStates = oldLocalUpdate.states;
long oldLocalUpdateVersion = oldLocalUpdate.updateVersion;
// Load and parse the latest remote update, if any
List<ClientState> remoteStates;
if (latest.remote == null) remoteStates = emptyList();
else remoteStates = loadUpdate(txn, latest.remote.messageId).states;
// Update the local states if necessary
List<ClientState> newLocalStates =
updateStatesFromLocalVersions(oldLocalStates, versions);
newLocalStates = updateStatesFromRemoteStates(newLocalStates,
remoteStates);
if (!oldLocalStates.equals(newLocalStates)) {
// Delete the latest local update
db.deleteMessage(txn, latest.local.messageId);
db.deleteMessageMetadata(txn, latest.local.messageId);
// Store a new local update
storeUpdate(txn, g.getId(), newLocalStates,
oldLocalUpdateVersion + 1);
}
// Calculate the old and new client visibilities
Map<ClientMajorVersion, Visibility> before =
getVisibilities(oldLocalStates, remoteStates);
Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, remoteStates);
// Call hooks for any visibilities that have changed
callVisibilityHooks(txn, c, before, after);
} catch (FormatException e) {
throw new DbException(e);
}
}
private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID,
MAJOR_VERSION, c);
}
private LatestUpdates findLatestUpdates(Transaction txn, GroupId g)
throws DbException, FormatException {
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
LatestUpdate local = null, remote = null;
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
long updateVersion = meta.getLong(MSG_KEY_UPDATE_VERSION);
if (meta.getBoolean(MSG_KEY_LOCAL))
local = new LatestUpdate(e.getKey(), updateVersion);
else remote = new LatestUpdate(e.getKey(), updateVersion);
}
return new LatestUpdates(local, remote);
}
private Update loadUpdate(Transaction txn, MessageId m) throws DbException {
try {
BdfList body = clientHelper.getMessageAsList(txn, m);
if (body == null) throw new DbException();
return parseUpdate(body);
} catch (FormatException e) {
throw new DbException(e);
}
}
private Update parseUpdate(BdfList body) throws FormatException {
List<ClientState> states = parseClientStates(body);
long updateVersion = parseUpdateVersion(body);
return new Update(states, updateVersion);
}
private List<ClientState> parseClientStates(BdfList body)
throws FormatException {
// Client states, update version
BdfList states = body.getList(0);
int size = states.size();
List<ClientState> parsed = new ArrayList<>(size);
for (int i = 0; i < size; i++)
parsed.add(parseClientState(states.getList(i)));
return parsed;
}
private ClientState parseClientState(BdfList clientState)
throws FormatException {
// Client ID, major version, minor version, active
ClientId clientId = new ClientId(clientState.getString(0));
int majorVersion = clientState.getLong(1).intValue();
int minorVersion = clientState.getLong(2).intValue();
boolean active = clientState.getBoolean(3);
return new ClientState(clientId, majorVersion, minorVersion, active);
}
private long parseUpdateVersion(BdfList body) throws FormatException {
// Client states, update version
return body.getLong(1);
}
private List<ClientState> updateStatesFromLocalVersions(
List<ClientState> oldStates, List<ClientVersion> newVersions) {
Map<ClientMajorVersion, ClientState> oldMap = new HashMap<>();
for (ClientState cs : oldStates) oldMap.put(cs.majorVersion, cs);
List<ClientState> newStates = new ArrayList<>(newVersions.size());
for (ClientVersion newVersion : newVersions) {
ClientState oldState = oldMap.get(newVersion.majorVersion);
boolean active = oldState != null && oldState.active;
newStates.add(new ClientState(newVersion.majorVersion,
newVersion.minorVersion, active));
}
return newStates;
}
private void storeUpdate(Transaction txn, GroupId g,
List<ClientState> states, long updateVersion) throws DbException {
try {
BdfList body = encodeUpdate(states, updateVersion);
long now = clock.currentTimeMillis();
Message m = clientHelper.createMessage(g, now, body);
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
meta.put(MSG_KEY_LOCAL, true);
clientHelper.addLocalMessage(txn, m, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
private BdfList encodeUpdate(List<ClientState> states, long updateVersion) {
BdfList encoded = new BdfList();
for (ClientState cs : states) encoded.add(encodeClientState(cs));
return BdfList.of(encoded, updateVersion);
}
private BdfList encodeClientState(ClientState cs) {
return BdfList.of(cs.majorVersion.getClientId().getString(),
cs.majorVersion.getMajorVersion(), cs.minorVersion, cs.active);
}
private Map<ClientMajorVersion, Visibility> getVisibilities(
List<ClientState> localStates, List<ClientState> remoteStates) {
Map<ClientMajorVersion, ClientState> remoteMap = new HashMap<>();
for (ClientState cs : remoteStates) remoteMap.put(cs.majorVersion, cs);
Map<ClientMajorVersion, Visibility> visibilities = new HashMap<>();
for (ClientState local : localStates) {
ClientState remote = remoteMap.get(local.majorVersion);
if (remote == null) visibilities.put(local.majorVersion, INVISIBLE);
else if (remote.active)
visibilities.put(local.majorVersion, SHARED);
else visibilities.put(local.majorVersion, VISIBLE);
}
return visibilities;
}
private void callVisibilityHooks(Transaction txn, Contact c,
Map<ClientMajorVersion, Visibility> before,
Map<ClientMajorVersion, Visibility> after) throws DbException {
Set<ClientMajorVersion> keys = new TreeSet<>();
keys.addAll(before.keySet());
keys.addAll(after.keySet());
for (ClientMajorVersion cv : keys) {
Visibility vBefore = before.get(cv), vAfter = after.get(cv);
if (vAfter == null) {
callVisibilityHook(txn, cv, c, INVISIBLE);
} else if (vBefore == null || !vBefore.equals(vAfter)) {
callVisibilityHook(txn, cv, c, vAfter);
}
}
}
private void callVisibilityHook(Transaction txn, ClientMajorVersion cv,
Contact c, Visibility v) throws DbException {
ClientVersioningHook hook = hooks.get(cv);
if (hook != null) hook.onClientVisibilityChanging(txn, c, v);
}
private void storeFirstUpdate(Transaction txn, GroupId g,
List<ClientVersion> versions) throws DbException {
List<ClientState> states = new ArrayList<>(versions.size());
for (ClientVersion cv : versions) {
states.add(new ClientState(cv.majorVersion, cv.minorVersion,
false));
}
storeUpdate(txn, g, states, 1);
}
private Contact getContact(Transaction txn, GroupId g) throws DbException {
try {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g);
int id = meta.getLong(GROUP_KEY_CONTACT_ID).intValue();
return db.getContact(txn, new ContactId(id));
} catch (FormatException e) {
throw new DbException(e);
}
}
private List<ClientState> updateStatesFromRemoteStates(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
Set<ClientMajorVersion> remoteSet = new HashSet<>();
for (ClientState cs : remoteStates) remoteSet.add(cs.majorVersion);
List<ClientState> newLocalStates =
new ArrayList<>(oldLocalStates.size());
for (ClientState oldState : oldLocalStates) {
boolean active = remoteSet.contains(oldState.majorVersion);
newLocalStates.add(new ClientState(oldState.majorVersion,
oldState.minorVersion, active));
}
return newLocalStates;
}
private static class Update {
private final List<ClientState> states;
private final long updateVersion;
private Update(List<ClientState> states, long updateVersion) {
this.states = states;
this.updateVersion = updateVersion;
}
}
private static class LatestUpdate {
private final MessageId messageId;
private final long updateVersion;
private LatestUpdate(MessageId messageId, long updateVersion) {
this.messageId = messageId;
this.updateVersion = updateVersion;
}
}
private static class LatestUpdates {
@Nullable
private final LatestUpdate local, remote;
private LatestUpdates(@Nullable LatestUpdate local,
@Nullable LatestUpdate remote) {
this.local = local;
this.remote = remote;
}
}
private static class ClientVersion implements Comparable<ClientVersion> {
private final ClientMajorVersion majorVersion;
private final int minorVersion;
private ClientVersion(ClientMajorVersion majorVersion,
int minorVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
}
private ClientVersion(ClientId clientId, int majorVersion,
int minorVersion) {
this(new ClientMajorVersion(clientId, majorVersion), minorVersion);
}
@Override
public boolean equals(Object o) {
if (o instanceof ClientVersion) {
ClientVersion cv = (ClientVersion) o;
return majorVersion.equals(cv.majorVersion)
&& minorVersion == cv.minorVersion;
}
return false;
}
@Override
public int hashCode() {
return majorVersion.hashCode();
}
@Override
public int compareTo(ClientVersion cv) {
int compare = majorVersion.compareTo(cv.majorVersion);
if (compare != 0) return compare;
return minorVersion - cv.minorVersion;
}
}
private static class ClientState {
private final ClientMajorVersion majorVersion;
private final int minorVersion;
private final boolean active;
private ClientState(ClientMajorVersion majorVersion, int minorVersion,
boolean active) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
this.active = active;
}
private ClientState(ClientId clientId, int majorVersion,
int minorVersion, boolean active) {
this(new ClientMajorVersion(clientId, majorVersion), minorVersion,
active);
}
@Override
public boolean equals(Object o) {
if (o instanceof ClientState) {
ClientState cs = (ClientState) o;
return majorVersion.equals(cs.majorVersion)
&& minorVersion == cs.minorVersion
&& active == cs.active;
}
return false;
}
@Override
public int hashCode() {
return majorVersion.hashCode();
}
}
}

View File

@@ -0,0 +1,61 @@
package org.briarproject.bramble.versioning;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.BdfMessageValidator;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@Immutable
@NotNullByDefault
class ClientVersioningValidator extends BdfMessageValidator {
ClientVersioningValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
}
@Override
protected BdfMessageContext validateMessage(Message m, Group g,
BdfList body) throws FormatException {
// Client states, update version
checkSize(body, 2);
// Client states
BdfList states = body.getList(0);
int size = states.size();
for (int i = 0; i < size; i++) {
BdfList clientState = states.getList(i);
// Client ID, major version, minor version, active
checkSize(clientState, 4);
String clientId = clientState.getString(0);
checkLength(clientId, 1, MAX_CLIENT_ID_LENGTH);
int majorVersion = clientState.getLong(1).intValue();
if (majorVersion < 0) throw new FormatException();
int minorVersion = clientState.getLong(2).intValue();
if (minorVersion < 0) throw new FormatException();
clientState.getBoolean(3);
}
// Update version
long updateVersion = body.getLong(1);
if (updateVersion < 0) throw new FormatException();
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
meta.put(MSG_KEY_LOCAL, false);
return new BdfMessageContext(meta);
}
}

View File

@@ -0,0 +1,56 @@
package org.briarproject.bramble.versioning;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
@Module
public class VersioningModule {
public static class EagerSingletons {
@Inject
ClientVersioningManager clientVersioningManager;
@Inject
ClientVersioningValidator clientVersioningValidator;
}
@Provides
@Singleton
ClientVersioningManager provideClientVersioningManager(
ClientVersioningManagerImpl clientVersioningManager,
LifecycleManager lifecycleManager, ContactManager contactManager,
ValidationManager validationManager) {
lifecycleManager.registerClient(clientVersioningManager);
lifecycleManager.registerService(clientVersioningManager);
contactManager.registerContactHook(clientVersioningManager);
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
clientVersioningManager);
return clientVersioningManager;
}
@Provides
@Singleton
ClientVersioningValidator provideClientVersioningValidator(
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock, ValidationManager validationManager) {
ClientVersioningValidator validator = new ClientVersioningValidator(
clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
validator);
return validator;
}
}

View File

@@ -30,6 +30,7 @@ import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; 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.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.event.GroupAddedEvent; import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
@@ -77,6 +78,7 @@ import static org.briarproject.bramble.test.TestUtils.getTransportId;
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.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
public class DatabaseComponentImplTest extends BrambleMockTestCase { public class DatabaseComponentImplTest extends BrambleMockTestCase {
@@ -89,6 +91,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final Object txn = new Object(); private final Object txn = new Object();
private final ClientId clientId; private final ClientId clientId;
private final int majorVersion;
private final GroupId groupId; private final GroupId groupId;
private final Group group; private final Group group;
private final Author author; private final Author author;
@@ -106,7 +109,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
public DatabaseComponentImplTest() { public DatabaseComponentImplTest() {
clientId = getClientId(); clientId = getClientId();
group = getGroup(clientId); majorVersion = 123;
group = getGroup(clientId, majorVersion);
groupId = group.getId(); groupId = group.getId();
author = getAuthor(); author = getAuthor();
localAuthor = getLocalAuthor(); localAuthor = getLocalAuthor();
@@ -175,7 +179,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
will(returnValue(true)); will(returnValue(true));
// getGroups() // getGroups()
oneOf(database).getGroups(txn, clientId); oneOf(database).getGroups(txn, clientId, majorVersion);
will(returnValue(singletonList(group))); will(returnValue(singletonList(group)));
// removeGroup() // removeGroup()
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
@@ -215,7 +219,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.addGroup(transaction, group); // First time - listeners called db.addGroup(transaction, group); // First time - listeners called
db.addGroup(transaction, group); // Second time - not called db.addGroup(transaction, group); // Second time - not called
assertEquals(singletonList(group), assertEquals(singletonList(group),
db.getGroups(transaction, clientId)); 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.removeLocalAuthor(transaction, localAuthor.getId());
@@ -1315,8 +1319,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys(); TransportKeys transportKeys = createTransportKeys();
Collection<KeySet> keys = KeySet ks = new KeySet(keySetId, contactId, transportKeys);
singletonList(new KeySet(keySetId, contactId, transportKeys)); Collection<KeySet> keys = singletonList(ks);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -1324,7 +1329,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// updateTransportKeys() // updateTransportKeys()
oneOf(database).containsTransport(txn, transportId); oneOf(database).containsTransport(txn, transportId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).updateTransportKeys(txn, keys); oneOf(database).updateTransportKeys(txn, ks);
// getTransportKeys() // getTransportKeys()
oneOf(database).containsTransport(txn, transportId); oneOf(database).containsTransport(txn, transportId);
will(returnValue(true)); will(returnValue(true));
@@ -1346,6 +1351,114 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
} }
@Test
public void testGetMessageStatusByGroupId() throws Exception {
MessageStatus status =
new MessageStatus(messageId, contactId, true, true);
context.checking(new Expectations() {{
// startTransaction()
oneOf(database).startTransaction();
will(returnValue(txn));
// getMessageStatus()
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(VISIBLE));
oneOf(database).getMessageStatus(txn, contactId, groupId);
will(returnValue(singletonList(status)));
// getMessageStatus() again
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(INVISIBLE));
oneOf(database).getMessageIds(txn, groupId);
will(returnValue(singletonList(messageId)));
// endTransaction()
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
Transaction transaction = db.startTransaction(true);
try {
// With visible group - return stored status
Collection<MessageStatus> statuses =
db.getMessageStatus(transaction, contactId, groupId);
assertEquals(1, statuses.size());
MessageStatus s = statuses.iterator().next();
assertEquals(messageId, s.getMessageId());
assertEquals(contactId, s.getContactId());
assertTrue(s.isSent());
assertTrue(s.isSeen());
// With invisible group - return default status
statuses = db.getMessageStatus(transaction, contactId, groupId);
assertEquals(1, statuses.size());
s = statuses.iterator().next();
assertEquals(messageId, s.getMessageId());
assertEquals(contactId, s.getContactId());
assertFalse(s.isSent());
assertFalse(s.isSeen());
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
}
}
@Test
public void testGetMessageStatusByMessageId() throws Exception {
MessageStatus status =
new MessageStatus(messageId, contactId, true, true);
context.checking(new Expectations() {{
// startTransaction()
oneOf(database).startTransaction();
will(returnValue(txn));
// getMessageStatus()
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(true));
oneOf(database).getMessageStatus(txn, contactId, messageId);
will(returnValue(status));
// getMessageStatus() again
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(true));
oneOf(database).getMessageStatus(txn, contactId, messageId);
will(returnValue(null));
// endTransaction()
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
Transaction transaction = db.startTransaction(true);
try {
// With visible group - return stored status
MessageStatus s =
db.getMessageStatus(transaction, contactId, messageId);
assertEquals(messageId, s.getMessageId());
assertEquals(contactId, s.getContactId());
assertTrue(s.isSent());
assertTrue(s.isSeen());
// With invisible group - return default status
s = db.getMessageStatus(transaction, contactId, messageId);
assertEquals(messageId, s.getMessageId());
assertEquals(contactId, s.getContactId());
assertFalse(s.isSent());
assertFalse(s.isSeen());
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
}
}
private TransportKeys createTransportKeys() { private TransportKeys createTransportKeys() {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();

View File

@@ -267,7 +267,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
String name = "getGroups(T, ClientId)"; String name = "getGroups(T, ClientId)";
benchmark(name, db -> { benchmark(name, db -> {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.getGroups(txn, pickRandom(clientIds)); db.getGroups(txn, pickRandom(clientIds), 123);
db.commitTransaction(txn); db.commitTransaction(txn);
}); });
} }
@@ -550,7 +550,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
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++) {
Group g = getGroup(clientIds.get(j % CLIENTS)); Group g = getGroup(clientIds.get(j % CLIENTS), 123);
groups.add(g); groups.add(g);
messageMeta.put(g.getId(), new ArrayList<>()); messageMeta.put(g.getId(), new ArrayList<>());
contactGroups.get(c).add(g); contactGroups.get(c).add(g);
@@ -584,7 +584,7 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
} }
} }
for (int i = 0; i < LOCAL_GROUPS; i++) { for (int i = 0; i < LOCAL_GROUPS; i++) {
Group g = getGroup(clientIds.get(i % CLIENTS)); Group g = getGroup(clientIds.get(i % CLIENTS), 123);
groups.add(g); groups.add(g);
messageMeta.put(g.getId(), new ArrayList<>()); messageMeta.put(g.getId(), new ArrayList<>());
groupMessages.put(g.getId(), new ArrayList<>()); groupMessages.put(g.getId(), new ArrayList<>());

View File

@@ -82,6 +82,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = TestUtils.getTestDirectory();
private final GroupId groupId; private final GroupId groupId;
private final ClientId clientId; private final ClientId clientId;
private final int majorVersion;
private final Group group; private final Group group;
private final Author author; private final Author author;
private final LocalAuthor localAuthor; private final LocalAuthor localAuthor;
@@ -96,7 +97,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
JdbcDatabaseTest() throws Exception { JdbcDatabaseTest() throws Exception {
clientId = getClientId(); clientId = getClientId();
group = getGroup(clientId); majorVersion = 123;
group = getGroup(clientId, majorVersion);
groupId = group.getId(); groupId = group.getId();
author = getAuthor(); author = getAuthor();
localAuthor = getLocalAuthor(); localAuthor = getLocalAuthor();
@@ -667,8 +669,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
TransportKeys keys = createTransportKeys(); long rotationPeriod = 123, rotationPeriod1 = 234;
TransportKeys keys1 = createTransportKeys(); TransportKeys keys = createTransportKeys(rotationPeriod);
TransportKeys keys1 = createTransportKeys(rotationPeriod1);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -697,6 +700,25 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
} }
} }
// Rotate the transport keys
TransportKeys rotated = createTransportKeys(rotationPeriod + 1);
TransportKeys rotated1 = createTransportKeys(rotationPeriod1 + 1);
db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
db.updateTransportKeys(txn, new KeySet(keySetId1, contactId, rotated1));
// Retrieve the transport keys again
allKeys = db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size());
for (KeySet ks : allKeys) {
assertEquals(contactId, ks.getContactId());
if (ks.getKeySetId().equals(keySetId)) {
assertKeysEquals(rotated, ks.getTransportKeys());
} else {
assertEquals(keySetId1, ks.getKeySetId());
assertKeysEquals(rotated1, ks.getTransportKeys());
}
}
// Removing the contact should remove the transport keys // Removing the contact should remove the transport keys
db.removeContact(txn, contactId); db.removeContact(txn, contactId);
assertEquals(emptyList(), db.getTransportKeys(txn, transportId)); assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
@@ -707,8 +729,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testUnboundTransportKeys() throws Exception { public void testUnboundTransportKeys() throws Exception {
TransportKeys keys = createTransportKeys(); long rotationPeriod = 123, rotationPeriod1 = 234;
TransportKeys keys1 = createTransportKeys(); TransportKeys keys = createTransportKeys(rotationPeriod);
TransportKeys keys1 = createTransportKeys(rotationPeriod1);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -754,6 +777,26 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
} }
} }
// Rotate the transport keys
TransportKeys rotated = createTransportKeys(rotationPeriod + 1);
TransportKeys rotated1 = createTransportKeys(rotationPeriod1 + 1);
db.updateTransportKeys(txn, new KeySet(keySetId, contactId, rotated));
db.updateTransportKeys(txn, new KeySet(keySetId1, null, rotated1));
// Retrieve the transport keys again
allKeys = db.getTransportKeys(txn, transportId);
assertEquals(2, allKeys.size());
for (KeySet ks : allKeys) {
if (ks.getKeySetId().equals(keySetId)) {
assertEquals(contactId, ks.getContactId());
assertKeysEquals(rotated, ks.getTransportKeys());
} else {
assertEquals(keySetId1, ks.getKeySetId());
assertNull(ks.getContactId());
assertKeysEquals(rotated1, ks.getTransportKeys());
}
}
// Remove the unbound transport keys // Remove the unbound transport keys
db.removeTransportKeys(txn, transportId, keySetId1); db.removeTransportKeys(txn, transportId, keySetId1);
@@ -763,7 +806,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
KeySet ks = allKeys.iterator().next(); KeySet ks = allKeys.iterator().next();
assertEquals(keySetId, ks.getKeySetId()); assertEquals(keySetId, ks.getKeySetId());
assertEquals(contactId, ks.getContactId()); assertEquals(contactId, ks.getContactId());
assertKeysEquals(keys, ks.getTransportKeys()); assertKeysEquals(rotated, ks.getTransportKeys());
// Removing the transport should remove the remaining transport keys // Removing the transport should remove the remaining transport keys
db.removeTransport(txn, transportId); db.removeTransport(txn, transportId);
@@ -809,8 +852,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
@Test @Test
public void testIncrementStreamCounter() throws Exception { public void testIncrementStreamCounter() throws Exception {
TransportKeys keys = createTransportKeys(); long rotationPeriod = 123;
long rotationPeriod = keys.getCurrentOutgoingKeys().getRotationPeriod(); TransportKeys keys = createTransportKeys(rotationPeriod);
long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter(); long streamCounter = keys.getCurrentOutgoingKeys().getStreamCounter();
Database<Connection> db = open(false); Database<Connection> db = open(false);
@@ -821,8 +864,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(), assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
db.updateTransportKeys(txn, assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
singletonList(new KeySet(keySetId, contactId, keys)));
// Increment the stream counter twice and retrieve the transport keys // Increment the stream counter twice and retrieve the transport keys
db.incrementStreamCounter(txn, transportId, keySetId); db.incrementStreamCounter(txn, transportId, keySetId);
@@ -838,14 +880,21 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(rotationPeriod, outCurr.getRotationPeriod()); assertEquals(rotationPeriod, outCurr.getRotationPeriod());
assertEquals(streamCounter + 2, outCurr.getStreamCounter()); assertEquals(streamCounter + 2, outCurr.getStreamCounter());
// The rest of the keys should be unaffected
assertKeysEquals(keys.getPreviousIncomingKeys(),
k.getPreviousIncomingKeys());
assertKeysEquals(keys.getCurrentIncomingKeys(),
k.getCurrentIncomingKeys());
assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@Test @Test
public void testSetReorderingWindow() throws Exception { public void testSetReorderingWindow() throws Exception {
TransportKeys keys = createTransportKeys(); long rotationPeriod = 123;
long rotationPeriod = keys.getCurrentIncomingKeys().getRotationPeriod(); TransportKeys keys = createTransportKeys(rotationPeriod);
long base = keys.getCurrentIncomingKeys().getWindowBase(); long base = keys.getCurrentIncomingKeys().getWindowBase();
byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap(); byte[] bitmap = keys.getCurrentIncomingKeys().getWindowBitmap();
@@ -857,8 +906,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(), assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
db.updateTransportKeys(txn, assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
singletonList(new KeySet(keySetId, contactId, keys)));
// Update the reordering window and retrieve the transport keys // Update the reordering window and retrieve the transport keys
new Random().nextBytes(bitmap); new Random().nextBytes(bitmap);
@@ -876,6 +924,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(base + 1, inCurr.getWindowBase()); assertEquals(base + 1, inCurr.getWindowBase());
assertArrayEquals(bitmap, inCurr.getWindowBitmap()); assertArrayEquals(bitmap, inCurr.getWindowBitmap());
// The rest of the keys should be unaffected
assertKeysEquals(keys.getPreviousIncomingKeys(),
k.getPreviousIncomingKeys());
assertKeysEquals(keys.getNextIncomingKeys(), k.getNextIncomingKeys());
assertKeysEquals(keys.getCurrentOutgoingKeys(),
k.getCurrentOutgoingKeys());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@@ -973,8 +1028,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Attach some metadata to the group // Attach some metadata to the group
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
metadata.put("foo", new byte[]{'b', 'a', 'r'}); metadata.put("foo", new byte[] {'b', 'a', 'r'});
metadata.put("baz", new byte[]{'b', 'a', 'm'}); metadata.put("baz", new byte[] {'b', 'a', 'm'});
db.mergeGroupMetadata(txn, groupId, metadata); db.mergeGroupMetadata(txn, groupId, metadata);
// Retrieve the metadata for the group // Retrieve the metadata for the group
@@ -1012,8 +1067,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Attach some metadata to the message // Attach some metadata to the message
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
metadata.put("foo", new byte[]{'b', 'a', 'r'}); metadata.put("foo", new byte[] {'b', 'a', 'r'});
metadata.put("baz", new byte[]{'b', 'a', 'm'}); metadata.put("baz", new byte[] {'b', 'a', 'm'});
db.mergeMessageMetadata(txn, messageId, metadata); db.mergeMessageMetadata(txn, messageId, metadata);
// Retrieve the metadata for the message // Retrieve the metadata for the message
@@ -1083,8 +1138,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Attach some metadata to the message // Attach some metadata to the message
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
metadata.put("foo", new byte[]{'b', 'a', 'r'}); metadata.put("foo", new byte[] {'b', 'a', 'r'});
metadata.put("baz", new byte[]{'b', 'a', 'm'}); metadata.put("baz", new byte[] {'b', 'a', 'm'});
db.mergeMessageMetadata(txn, messageId, metadata); db.mergeMessageMetadata(txn, messageId, metadata);
// Retrieve the metadata for the message // Retrieve the metadata for the message
@@ -1145,11 +1200,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Attach some metadata to the messages // Attach some metadata to the messages
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
metadata.put("foo", new byte[]{'b', 'a', 'r'}); metadata.put("foo", new byte[] {'b', 'a', 'r'});
metadata.put("baz", new byte[]{'b', 'a', 'm'}); metadata.put("baz", new byte[] {'b', 'a', 'm'});
db.mergeMessageMetadata(txn, messageId, metadata); db.mergeMessageMetadata(txn, messageId, metadata);
Metadata metadata1 = new Metadata(); Metadata metadata1 = new Metadata();
metadata1.put("foo", new byte[]{'q', 'u', 'x'}); metadata1.put("foo", new byte[] {'q', 'u', 'x'});
db.mergeMessageMetadata(txn, messageId1, metadata1); db.mergeMessageMetadata(txn, messageId1, metadata1);
// Retrieve all the metadata for the group // Retrieve all the metadata for the group
@@ -1249,11 +1304,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Attach some metadata to the messages // Attach some metadata to the messages
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
metadata.put("foo", new byte[]{'b', 'a', 'r'}); metadata.put("foo", new byte[] {'b', 'a', 'r'});
metadata.put("baz", new byte[]{'b', 'a', 'm'}); metadata.put("baz", new byte[] {'b', 'a', 'm'});
db.mergeMessageMetadata(txn, messageId, metadata); db.mergeMessageMetadata(txn, messageId, metadata);
Metadata metadata1 = new Metadata(); Metadata metadata1 = new Metadata();
metadata1.put("foo", new byte[]{'b', 'a', 'r'}); metadata1.put("foo", new byte[] {'b', 'a', 'r'});
db.mergeMessageMetadata(txn, messageId1, metadata1); db.mergeMessageMetadata(txn, messageId1, metadata1);
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
@@ -1264,7 +1319,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
} else { } else {
// Query for foo // Query for foo
query = new Metadata(); query = new Metadata();
query.put("foo", new byte[]{'b', 'a', 'r'}); query.put("foo", new byte[] {'b', 'a', 'r'});
} }
db.setMessageState(txn, messageId, DELIVERED); db.setMessageState(txn, messageId, DELIVERED);
@@ -1407,7 +1462,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, PENDING, true, contactId); db.addMessage(txn, message, PENDING, true, contactId);
// Add a second group // Add a second group
Group group1 = getGroup(clientId); Group group1 = getGroup(clientId, 123);
GroupId groupId1 = group1.getId(); GroupId groupId1 = group1.getId();
db.addGroup(txn, group1); db.addGroup(txn, group1);
@@ -1541,6 +1596,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// The message should not be sent or seen // The message should not be sent or seen
MessageStatus status = db.getMessageStatus(txn, contactId, messageId); MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
assertNotNull(status);
assertEquals(messageId, status.getMessageId()); assertEquals(messageId, status.getMessageId());
assertEquals(contactId, status.getContactId()); assertEquals(contactId, status.getContactId());
assertFalse(status.isSent()); assertFalse(status.isSent());
@@ -1561,6 +1617,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// The message should be sent but not seen // The message should be sent but not seen
status = db.getMessageStatus(txn, contactId, messageId); status = db.getMessageStatus(txn, contactId, messageId);
assertNotNull(status);
assertEquals(messageId, status.getMessageId()); assertEquals(messageId, status.getMessageId());
assertEquals(contactId, status.getContactId()); assertEquals(contactId, status.getContactId());
assertTrue(status.isSent()); assertTrue(status.isSent());
@@ -1580,6 +1637,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// The message should be sent and seen // The message should be sent and seen
status = db.getMessageStatus(txn, contactId, messageId); status = db.getMessageStatus(txn, contactId, messageId);
assertNotNull(status);
assertEquals(messageId, status.getMessageId()); assertEquals(messageId, status.getMessageId());
assertEquals(contactId, status.getContactId()); assertEquals(contactId, status.getContactId());
assertTrue(status.isSent()); assertTrue(status.isSent());
@@ -1594,6 +1652,36 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertTrue(status.isSent()); assertTrue(status.isSent());
assertTrue(status.isSeen()); assertTrue(status.isSeen());
// Make the group invisible to the contact
db.removeGroupVisibility(txn, contactId, groupId);
// Null should be returned when querying by message
assertNull(db.getMessageStatus(txn, contactId, messageId));
// No statuses should be returned when querying by group
statuses = db.getMessageStatus(txn, contactId, groupId);
assertEquals(0, statuses.size());
// Make the group visible to the contact again
db.addGroupVisibility(txn, contactId, groupId, false);
// The default status should be returned when querying by message
status = db.getMessageStatus(txn, contactId, messageId);
assertNotNull(status);
assertEquals(messageId, status.getMessageId());
assertEquals(contactId, status.getContactId());
assertFalse(status.isSent());
assertFalse(status.isSeen());
// The default status should be returned when querying by group
statuses = db.getMessageStatus(txn, contactId, groupId);
assertEquals(1, statuses.size());
status = statuses.iterator().next();
assertEquals(messageId, status.getMessageId());
assertEquals(contactId, status.getContactId());
assertFalse(status.isSent());
assertFalse(status.isSeen());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@@ -1775,6 +1863,22 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.close(); db.close();
} }
@Test
public void testGetGroups() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
assertEquals(emptyList(), db.getGroups(txn, clientId, majorVersion));
db.addGroup(txn, group);
assertEquals(singletonList(group),
db.getGroups(txn, clientId, majorVersion));
db.removeGroup(txn, groupId);
assertEquals(emptyList(), db.getGroups(txn, clientId, majorVersion));
db.commitTransaction(txn);
db.close();
}
@Test @Test
public void testExceptionHandling() throws Exception { public void testExceptionHandling() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);
@@ -1804,23 +1908,23 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
return db; return db;
} }
private TransportKeys createTransportKeys() { private TransportKeys createTransportKeys(long rotationPeriod) {
SecretKey inPrevTagKey = getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
1, 123, new byte[4]); rotationPeriod - 1, 123, new byte[4]);
SecretKey inCurrTagKey = getSecretKey(); SecretKey inCurrTagKey = getSecretKey();
SecretKey inCurrHeaderKey = getSecretKey(); SecretKey inCurrHeaderKey = getSecretKey();
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey, IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
2, 234, new byte[4]); rotationPeriod, 234, new byte[4]);
SecretKey inNextTagKey = getSecretKey(); SecretKey inNextTagKey = getSecretKey();
SecretKey inNextHeaderKey = getSecretKey(); SecretKey inNextHeaderKey = getSecretKey();
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey, IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
3, 345, new byte[4]); rotationPeriod + 1, 345, new byte[4]);
SecretKey outCurrTagKey = getSecretKey(); SecretKey outCurrTagKey = getSecretKey();
SecretKey outCurrHeaderKey = getSecretKey(); SecretKey outCurrHeaderKey = getSecretKey();
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
2, 456, true); rotationPeriod, 456, true);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
} }

View File

@@ -5,23 +5,31 @@ import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.record.RecordReaderFactory;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.util.ByteUtils;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.EOFException;
import java.io.ByteArrayOutputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicReference;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT; import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM; import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY; import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
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.assertNotNull;
public class KeyAgreementTransportTest extends BrambleMockTestCase { public class KeyAgreementTransportTest extends BrambleMockTestCase {
@@ -31,222 +39,268 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
context.mock(TransportConnectionReader.class); context.mock(TransportConnectionReader.class);
private final TransportConnectionWriter transportConnectionWriter = private final TransportConnectionWriter transportConnectionWriter =
context.mock(TransportConnectionWriter.class); context.mock(TransportConnectionWriter.class);
private final RecordReaderFactory recordReaderFactory =
context.mock(RecordReaderFactory.class);
private final RecordWriterFactory recordWriterFactory =
context.mock(RecordWriterFactory.class);
private final RecordReader recordReader = context.mock(RecordReader.class);
private final RecordWriter recordWriter = context.mock(RecordWriter.class);
private final TransportId transportId = getTransportId(); private final TransportId transportId = getTransportId();
private final KeyAgreementConnection keyAgreementConnection = private final KeyAgreementConnection keyAgreementConnection =
new KeyAgreementConnection(duplexTransportConnection, transportId); new KeyAgreementConnection(duplexTransportConnection, transportId);
private ByteArrayInputStream inputStream; private final InputStream inputStream;
private ByteArrayOutputStream outputStream; private final OutputStream outputStream;
private KeyAgreementTransport kat; private KeyAgreementTransport kat;
public KeyAgreementTransportTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
inputStream = context.mock(InputStream.class);
outputStream = context.mock(OutputStream.class);
}
@Test @Test
public void testSendKey() throws Exception { public void testSendKey() throws Exception {
setup(new byte[0]); byte[] key = getRandomBytes(123);
byte[] key = TestUtils.getRandomBytes(123);
setup();
AtomicReference<Record> written = expectWriteRecord();
kat.sendKey(key); kat.sendKey(key);
assertRecordSent(KEY, key); assertNotNull(written.get());
assertRecordEquals(PROTOCOL_VERSION, KEY, key, written.get());
} }
@Test @Test
public void testSendConfirm() throws Exception { public void testSendConfirm() throws Exception {
setup(new byte[0]); byte[] confirm = getRandomBytes(123);
byte[] confirm = TestUtils.getRandomBytes(123);
setup();
AtomicReference<Record> written = expectWriteRecord();
kat.sendConfirm(confirm); kat.sendConfirm(confirm);
assertRecordSent(CONFIRM, confirm); assertNotNull(written.get());
assertRecordEquals(PROTOCOL_VERSION, CONFIRM, confirm, written.get());
} }
@Test @Test
public void testSendAbortWithException() throws Exception { public void testSendAbortWithException() throws Exception {
setup(new byte[0]); setup();
AtomicReference<Record> written = expectWriteRecord();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(true, true); oneOf(transportConnectionReader).dispose(true, true);
oneOf(transportConnectionWriter).dispose(true); oneOf(transportConnectionWriter).dispose(true);
}}); }});
kat.sendAbort(true); kat.sendAbort(true);
assertRecordSent(ABORT, new byte[0]); assertNotNull(written.get());
assertRecordEquals(PROTOCOL_VERSION, ABORT, new byte[0], written.get());
} }
@Test @Test
public void testSendAbortWithoutException() throws Exception { public void testSendAbortWithoutException() throws Exception {
setup(new byte[0]); setup();
AtomicReference<Record> written = expectWriteRecord();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(transportConnectionReader).dispose(false, true); oneOf(transportConnectionReader).dispose(false, true);
oneOf(transportConnectionWriter).dispose(false); oneOf(transportConnectionWriter).dispose(false);
}}); }});
kat.sendAbort(false); kat.sendAbort(false);
assertRecordSent(ABORT, new byte[0]); assertNotNull(written.get());
assertRecordEquals(PROTOCOL_VERSION, ABORT, new byte[0], written.get());
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfAtEndOfStream() public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
throws Exception { throws Exception {
setup(new byte[0]); setup();
kat.receiveKey(); context.checking(new Expectations() {{
} oneOf(recordReader).readRecord();
will(throwException(new EOFException()));
}});
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfHeaderIsTooShort()
throws Exception {
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
input[0] = PROTOCOL_VERSION;
input[1] = KEY;
setup(input);
kat.receiveKey();
}
@Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfPayloadIsTooShort()
throws Exception {
int payloadLength = 123;
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
input[0] = PROTOCOL_VERSION;
input[1] = KEY;
ByteUtils.writeUint16(payloadLength, input, 2);
setup(input);
kat.receiveKey(); kat.receiveKey();
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfProtocolVersionIsUnrecognised() public void testReceiveKeyThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception { throws Exception {
setup(createRecord((byte) (PROTOCOL_VERSION + 1), KEY, new byte[123])); byte unknownVersion = (byte) (PROTOCOL_VERSION + 1);
byte[] key = getRandomBytes(123);
setup();
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(new Record(unknownVersion, KEY, key)));
}});
kat.receiveKey(); kat.receiveKey();
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfAbortIsReceived() public void testReceiveKeyThrowsExceptionIfAbortIsReceived()
throws Exception { throws Exception {
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0])); setup();
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(new Record(PROTOCOL_VERSION, ABORT, new byte[0])));
}});
kat.receiveKey(); kat.receiveKey();
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfConfirmIsReceived() public void testReceiveKeyThrowsExceptionIfConfirmIsReceived()
throws Exception { throws Exception {
setup(createRecord(PROTOCOL_VERSION, CONFIRM, new byte[123])); byte[] confirm = getRandomBytes(123);
setup();
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(new Record(PROTOCOL_VERSION, CONFIRM, confirm)));
}});
kat.receiveKey(); kat.receiveKey();
} }
@Test @Test
public void testReceiveKeySkipsUnrecognisedRecordTypes() throws Exception { public void testReceiveKeySkipsUnrecognisedRecordTypes() throws Exception {
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1), byte type1 = (byte) (ABORT + 1);
new byte[123]); byte[] payload1 = getRandomBytes(123);
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2), Record unknownRecord1 = new Record(PROTOCOL_VERSION, type1, payload1);
new byte[0]); byte type2 = (byte) (ABORT + 2);
byte[] payload = TestUtils.getRandomBytes(123); byte[] payload2 = new byte[0];
byte[] key = createRecord(PROTOCOL_VERSION, KEY, payload); Record unknownRecord2 = new Record(PROTOCOL_VERSION, type2, payload2);
ByteArrayOutputStream input = new ByteArrayOutputStream(); byte[] key = getRandomBytes(123);
input.write(skip1); Record keyRecord = new Record(PROTOCOL_VERSION, KEY, key);
input.write(skip2);
input.write(key); setup();
setup(input.toByteArray()); context.checking(new Expectations() {{
assertArrayEquals(payload, kat.receiveKey()); oneOf(recordReader).readRecord();
will(returnValue(unknownRecord1));
oneOf(recordReader).readRecord();
will(returnValue(unknownRecord2));
oneOf(recordReader).readRecord();
will(returnValue(keyRecord));
}});
assertArrayEquals(key, kat.receiveKey());
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream() public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
throws Exception { throws Exception {
setup(new byte[0]); setup();
kat.receiveConfirm(); context.checking(new Expectations() {{
} oneOf(recordReader).readRecord();
will(throwException(new EOFException()));
}});
@Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfHeaderIsTooShort()
throws Exception {
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
input[0] = PROTOCOL_VERSION;
input[1] = CONFIRM;
setup(input);
kat.receiveConfirm();
}
@Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfPayloadIsTooShort()
throws Exception {
int payloadLength = 123;
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
input[0] = PROTOCOL_VERSION;
input[1] = CONFIRM;
ByteUtils.writeUint16(payloadLength, input, 2);
setup(input);
kat.receiveConfirm(); kat.receiveConfirm();
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfProtocolVersionIsUnrecognised() public void testReceiveConfirmThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception { throws Exception {
setup(createRecord((byte) (PROTOCOL_VERSION + 1), CONFIRM, byte unknownVersion = (byte) (PROTOCOL_VERSION + 1);
new byte[123])); byte[] confirm = getRandomBytes(123);
setup();
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(new Record(unknownVersion, CONFIRM, confirm)));
}});
kat.receiveConfirm(); kat.receiveConfirm();
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveConfirmThrowsExceptionIfAbortIsReceived() public void testReceiveConfirmThrowsExceptionIfAbortIsReceived()
throws Exception { throws Exception {
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0])); setup();
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(new Record(PROTOCOL_VERSION, ABORT, new byte[0])));
}});
kat.receiveConfirm(); kat.receiveConfirm();
} }
@Test(expected = AbortException.class) @Test(expected = AbortException.class)
public void testReceiveKeyThrowsExceptionIfKeyIsReceived() public void testReceiveConfirmThrowsExceptionIfKeyIsReceived()
throws Exception { throws Exception {
setup(createRecord(PROTOCOL_VERSION, KEY, new byte[123])); byte[] key = getRandomBytes(123);
setup();
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(new Record(PROTOCOL_VERSION, KEY, key)));
}});
kat.receiveConfirm(); kat.receiveConfirm();
} }
@Test @Test
public void testReceiveConfirmSkipsUnrecognisedRecordTypes() public void testReceiveConfirmSkipsUnrecognisedRecordTypes()
throws Exception { throws Exception {
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1), byte type1 = (byte) (ABORT + 1);
new byte[123]); byte[] payload1 = getRandomBytes(123);
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2), Record unknownRecord1 = new Record(PROTOCOL_VERSION, type1, payload1);
new byte[0]); byte type2 = (byte) (ABORT + 2);
byte[] payload = TestUtils.getRandomBytes(123); byte[] payload2 = new byte[0];
byte[] confirm = createRecord(PROTOCOL_VERSION, CONFIRM, payload); Record unknownRecord2 = new Record(PROTOCOL_VERSION, type2, payload2);
ByteArrayOutputStream input = new ByteArrayOutputStream(); byte[] confirm = getRandomBytes(123);
input.write(skip1); Record confirmRecord = new Record(PROTOCOL_VERSION, CONFIRM, confirm);
input.write(skip2);
input.write(confirm); setup();
setup(input.toByteArray()); context.checking(new Expectations() {{
assertArrayEquals(payload, kat.receiveConfirm()); oneOf(recordReader).readRecord();
will(returnValue(unknownRecord1));
oneOf(recordReader).readRecord();
will(returnValue(unknownRecord2));
oneOf(recordReader).readRecord();
will(returnValue(confirmRecord));
}});
assertArrayEquals(confirm, kat.receiveConfirm());
} }
private void setup(byte[] input) throws Exception { private void setup() throws Exception {
inputStream = new ByteArrayInputStream(input);
outputStream = new ByteArrayOutputStream();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(duplexTransportConnection).getReader(); allowing(duplexTransportConnection).getReader();
will(returnValue(transportConnectionReader)); will(returnValue(transportConnectionReader));
allowing(transportConnectionReader).getInputStream(); allowing(transportConnectionReader).getInputStream();
will(returnValue(inputStream)); will(returnValue(inputStream));
oneOf(recordReaderFactory).createRecordReader(inputStream);
will(returnValue(recordReader));
allowing(duplexTransportConnection).getWriter(); allowing(duplexTransportConnection).getWriter();
will(returnValue(transportConnectionWriter)); will(returnValue(transportConnectionWriter));
allowing(transportConnectionWriter).getOutputStream(); allowing(transportConnectionWriter).getOutputStream();
will(returnValue(outputStream)); will(returnValue(outputStream));
oneOf(recordWriterFactory).createRecordWriter(outputStream);
will(returnValue(recordWriter));
}}); }});
kat = new KeyAgreementTransport(keyAgreementConnection); kat = new KeyAgreementTransport(recordReaderFactory,
recordWriterFactory, keyAgreementConnection);
} }
private void assertRecordSent(byte expectedType, byte[] expectedPayload) { private AtomicReference<Record> expectWriteRecord() throws Exception {
byte[] output = outputStream.toByteArray(); AtomicReference<Record> captured = new AtomicReference<>();
assertEquals(RECORD_HEADER_LENGTH + expectedPayload.length, context.checking(new Expectations() {{
output.length); oneOf(recordWriter).writeRecord(with(any(Record.class)));
assertEquals(PROTOCOL_VERSION, output[0]); will(new CaptureArgumentAction<>(captured, Record.class, 0));
assertEquals(expectedType, output[1]); oneOf(recordWriter).flush();
assertEquals(expectedPayload.length, ByteUtils.readUint16(output, 2)); }});
byte[] payload = new byte[output.length - RECORD_HEADER_LENGTH]; return captured;
System.arraycopy(output, RECORD_HEADER_LENGTH, payload, 0,
payload.length);
assertArrayEquals(expectedPayload, payload);
} }
private byte[] createRecord(byte version, byte type, byte[] payload) { private void assertRecordEquals(byte expectedVersion, byte expectedType,
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length]; byte[] expectedPayload, Record actual) {
b[0] = version; assertEquals(expectedVersion, actual.getProtocolVersion());
b[1] = type; assertEquals(expectedType, actual.getRecordType());
ByteUtils.writeUint16(payload.length, b, 2); assertArrayEquals(expectedPayload, actual.getPayload());
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
return b;
} }
} }

View File

@@ -19,6 +19,7 @@ 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.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
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;
@@ -29,8 +30,9 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
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.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
@@ -45,13 +47,15 @@ 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 ClientVersioningManager clientVersioningManager =
context.mock(ClientVersioningManager.class);
private final MetadataParser metadataParser = private final MetadataParser metadataParser =
context.mock(MetadataParser.class); context.mock(MetadataParser.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 Group localGroup = getGroup(CLIENT_ID); private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final LocalAuthor localAuthor = getLocalAuthor(); 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"),
@@ -77,49 +81,41 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
private TransportPropertyManagerImpl createInstance() { private TransportPropertyManagerImpl createInstance() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
CLIENT_VERSION); MAJOR_VERSION);
will(returnValue(localGroup)); will(returnValue(localGroup));
}}); }});
return new TransportPropertyManagerImpl(db, clientHelper, return new TransportPropertyManagerImpl(db, clientHelper,
metadataParser, contactGroupFactory, clock); clientVersioningManager, metadataParser, contactGroupFactory,
clock);
} }
@Test @Test
public void testCreatesGroupsAtStartup() throws Exception { public void testCreatesGroupsAtStartup() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact1 = getContact(true); Contact contact = getContact(true);
Contact contact2 = getContact(true); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
List<Contact> contacts = Arrays.asList(contact1, contact2);
Group contactGroup1 = getGroup(CLIENT_ID);
Group contactGroup2 = getGroup(CLIENT_ID);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
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(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(contacts)); will(returnValue(singletonList(contact)));
// The first contact's group has already been set up
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact1); MAJOR_VERSION, contact);
will(returnValue(contactGroup1)); will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup1.getId()); oneOf(db).addGroup(txn, contactGroup);
will(returnValue(true)); oneOf(clientVersioningManager).getClientVisibility(txn,
// The second contact's group hasn't been set up contact.getId(), CLIENT_ID, MAJOR_VERSION);
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, will(returnValue(SHARED));
CLIENT_VERSION, contact2); oneOf(db).setGroupVisibility(txn, contact.getId(),
will(returnValue(contactGroup2)); contactGroup.getId(), SHARED);
oneOf(db).containsGroup(txn, contactGroup2.getId());
will(returnValue(false));
oneOf(db).addGroup(txn, contactGroup2);
oneOf(db).setGroupVisibility(txn, contact2.getId(),
contactGroup2.getId(), SHARED);
}}); }});
// Copy the latest local properties into the group // Copy the latest local properties into the group
expectGetLocalProperties(txn); expectGetLocalProperties(txn);
expectStoreMessage(txn, contactGroup2.getId(), "foo", fooPropertiesDict, expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
1, true, true); 1, true, true);
expectStoreMessage(txn, contactGroup2.getId(), "bar", barPropertiesDict, expectStoreMessage(txn, contactGroup.getId(), "bar", barPropertiesDict,
1, true, true); 1, true, true);
TransportPropertyManagerImpl t = createInstance(); TransportPropertyManagerImpl t = createInstance();
@@ -144,16 +140,17 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
public void testCreatesContactGroupWhenAddingContact() throws Exception { public void testCreatesContactGroupWhenAddingContact() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact(true); Contact contact = getContact(true);
Group contactGroup = getGroup(CLIENT_ID); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
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(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup.getId());
will(returnValue(false));
oneOf(db).addGroup(txn, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(clientVersioningManager).getClientVisibility(txn,
contact.getId(), CLIENT_ID, MAJOR_VERSION);
will(returnValue(SHARED));
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED); contactGroup.getId(), SHARED);
}}); }});
@@ -172,11 +169,11 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
public void testRemovesGroupWhenRemovingContact() throws Exception { public void testRemovesGroupWhenRemovingContact() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact(true); Contact contact = getContact(true);
Group contactGroup = getGroup(CLIENT_ID); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(db).removeGroup(txn, contactGroup); oneOf(db).removeGroup(txn, contactGroup);
}}); }});
@@ -307,7 +304,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testStoresRemotePropertiesWithVersion0() throws Exception { public void testStoresRemotePropertiesWithVersion0() throws Exception {
Contact contact = getContact(true); Contact contact = getContact(true);
Group contactGroup = getGroup(CLIENT_ID); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Map<TransportId, TransportProperties> properties = Map<TransportId, TransportProperties> properties =
new LinkedHashMap<>(); new LinkedHashMap<>();
@@ -318,7 +315,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
oneOf(db).getContact(txn, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
}}); }});
expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict, expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
@@ -421,8 +418,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
Contact contact3 = getContact(true); Contact contact3 = getContact(true);
List<Contact> contacts = List<Contact> contacts =
Arrays.asList(contact1, contact2, contact3); Arrays.asList(contact1, contact2, contact3);
Group contactGroup2 = getGroup(CLIENT_ID); Group contactGroup2 = getGroup(CLIENT_ID, MAJOR_VERSION);
Group contactGroup3 = getGroup(CLIENT_ID); Group contactGroup3 = getGroup(CLIENT_ID, MAJOR_VERSION);
Map<MessageId, BdfDictionary> messageMetadata3 = Map<MessageId, BdfDictionary> messageMetadata3 =
new LinkedHashMap<>(); new LinkedHashMap<>();
// A remote update for another transport should be ignored // A remote update for another transport should be ignored
@@ -456,14 +453,14 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
// First contact: skipped because not active // First contact: skipped because not active
// Second contact: no updates // Second contact: no updates
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact2); MAJOR_VERSION, contact2);
will(returnValue(contactGroup2)); will(returnValue(contactGroup2));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup2.getId()); contactGroup2.getId());
will(returnValue(Collections.emptyMap())); will(returnValue(Collections.emptyMap()));
// Third contact: returns an update // Third contact: returns an update
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact3); MAJOR_VERSION, contact3);
will(returnValue(contactGroup3)); will(returnValue(contactGroup3));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup3.getId()); contactGroup3.getId());
@@ -524,7 +521,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
public void testMergingNewPropertiesCreatesUpdate() throws Exception { public void testMergingNewPropertiesCreatesUpdate() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact(true); Contact contact = getContact(true);
Group contactGroup = getGroup(CLIENT_ID); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
@@ -538,9 +535,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
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(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(Collections.singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId()); contactGroup.getId());
@@ -559,7 +556,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
public void testMergingUpdatedPropertiesCreatesUpdate() throws Exception { public void testMergingUpdatedPropertiesCreatesUpdate() throws Exception {
Transaction txn = new Transaction(null, false); Transaction txn = new Transaction(null, false);
Contact contact = getContact(true); Contact contact = getContact(true);
Group contactGroup = getGroup(CLIENT_ID); Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
BdfDictionary oldMetadata = BdfDictionary.of( BdfDictionary oldMetadata = BdfDictionary.of(
new BdfEntry("transportId", "foo"), new BdfEntry("transportId", "foo"),
new BdfEntry("version", 1), new BdfEntry("version", 1),
@@ -597,9 +594,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
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(db).getContacts(txn); oneOf(db).getContacts(txn);
will(returnValue(Collections.singletonList(contact))); will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
CLIENT_VERSION, contact); MAJOR_VERSION, contact);
will(returnValue(contactGroup)); will(returnValue(contactGroup));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId()); contactGroup.getId());

View File

@@ -18,7 +18,8 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -42,7 +43,7 @@ public class TransportPropertyValidatorTest extends BrambleMockTestCase {
transportProperties = new TransportProperties(); transportProperties = new TransportProperties();
transportProperties.put("foo", "bar"); transportProperties.put("foo", "bar");
group = getGroup(getClientId()); group = getGroup(CLIENT_ID, MAJOR_VERSION);
message = getMessage(group.getId()); message = getMessage(group.getId());
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class); MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);

View File

@@ -0,0 +1,102 @@
package org.briarproject.bramble.record;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class RecordReaderImplTest extends BrambleTestCase {
@Test
public void testAcceptsEmptyPayload() throws Exception {
// Version 1, type 2, payload length 0
byte[] header = new byte[] {1, 2, 0, 0};
ByteArrayInputStream in = new ByteArrayInputStream(header);
RecordReader reader = new RecordReaderImpl(in);
Record record = reader.readRecord();
assertEquals(1, record.getProtocolVersion());
assertEquals(2, record.getRecordType());
assertArrayEquals(new byte[0], record.getPayload());
}
@Test
public void testAcceptsMaxLengthPayload() throws Exception {
byte[] record =
new byte[RECORD_HEADER_BYTES + MAX_RECORD_PAYLOAD_BYTES];
// Version 1, type 2, payload length MAX_RECORD_PAYLOAD_BYTES
record[0] = 1;
record[1] = 2;
ByteUtils.writeUint16(MAX_RECORD_PAYLOAD_BYTES, record, 2);
ByteArrayInputStream in = new ByteArrayInputStream(record);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfPayloadLengthIsNegative()
throws Exception {
// Version 1, type 2, payload length -1
byte[] header = new byte[] {1, 2, (byte) 0xFF, (byte) 0xFF};
ByteArrayInputStream in = new ByteArrayInputStream(header);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfPayloadLengthIsTooLarge()
throws Exception {
// Version 1, type 2, payload length MAX_RECORD_PAYLOAD_BYTES + 1
byte[] header = new byte[] {1, 2, 0, 0};
ByteUtils.writeUint16(MAX_RECORD_PAYLOAD_BYTES + 1, header, 2);
ByteArrayInputStream in = new ByteArrayInputStream(header);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
@Test(expected = EOFException.class)
public void testEofExceptionIfProtocolVersionIsMissing() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
@Test(expected = EOFException.class)
public void testEofExceptionIfRecordTypeIsMissing() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[1]);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
@Test(expected = EOFException.class)
public void testEofExceptionIfPayloadLengthIsMissing() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[2]);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
@Test(expected = EOFException.class)
public void testEofExceptionIfPayloadLengthIsTruncated() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[3]);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
@Test(expected = EOFException.class)
public void testEofExceptionIfPayloadIsTruncated() throws Exception {
// Version 0, type 0, payload length 1
byte[] header = new byte[] {0, 0, 0, 1};
ByteArrayInputStream in = new ByteArrayInputStream(header);
RecordReader reader = new RecordReaderImpl(in);
reader.readRecord();
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.bramble.record;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordWriter;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class RecordWriterImplTest extends BrambleTestCase {
@Test
public void testWritesEmptyRecord() throws Exception {
testWritesRecord(0);
}
@Test
public void testWritesMaxLengthRecord() throws Exception {
testWritesRecord(MAX_RECORD_PAYLOAD_BYTES);
}
private void testWritesRecord(int payloadLength) throws Exception {
byte protocolVersion = 123;
byte recordType = 45;
byte[] payload = getRandomBytes(payloadLength);
ByteArrayOutputStream out = new ByteArrayOutputStream();
RecordWriter writer = new RecordWriterImpl(out);
writer.writeRecord(new Record(protocolVersion, recordType, payload));
writer.flush();
byte[] written = out.toByteArray();
assertEquals(RECORD_HEADER_BYTES + payloadLength, written.length);
assertEquals(protocolVersion, written[0]);
assertEquals(recordType, written[1]);
assertEquals(payloadLength, ByteUtils.readUint16(written, 2));
byte[] writtenPayload = new byte[payloadLength];
System.arraycopy(written, RECORD_HEADER_BYTES, writtenPayload, 0,
payloadLength);
assertArrayEquals(payload, writtenPayload);
}
}

View File

@@ -1,219 +0,0 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.ByteUtils;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class RecordReaderImplTest extends BrambleMockTestCase {
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
@Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
byte[] b = createAck(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
byte[] b = createAck(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsEmpty() throws Exception {
byte[] b = createEmptyAck();
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readAck();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
byte[] b = createOffer(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readOffer();
}
@Test
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
byte[] b = createOffer(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readOffer();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
byte[] b = createEmptyOffer();
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readOffer();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
byte[] b = createRequest(true);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readRequest();
}
@Test
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
byte[] b = createRequest(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readRequest();
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
byte[] b = createEmptyRequest();
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.readRequest();
}
@Test
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
assertTrue(reader.eof());
}
@Test
public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception {
byte[] b = createAck(false);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
assertFalse(reader.eof());
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfHeaderIsTooShort() throws Exception {
byte[] b = new byte[RECORD_HEADER_LENGTH - 1];
b[0] = PROTOCOL_VERSION;
b[1] = ACK;
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfPayloadIsTooShort() throws Exception {
int payloadLength = 123;
byte[] b = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
b[0] = PROTOCOL_VERSION;
b[1] = ACK;
ByteUtils.writeUint16(payloadLength, b, 2);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception {
byte version = (byte) (PROTOCOL_VERSION + 1);
byte[] b = createRecord(version, ACK, new byte[0]);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfPayloadIsTooLong() throws Exception {
byte[] payload = new byte[MAX_RECORD_PAYLOAD_LENGTH + 1];
byte[] b = createRecord(PROTOCOL_VERSION, ACK, payload);
ByteArrayInputStream in = new ByteArrayInputStream(b);
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
reader.eof();
}
@Test
public void testSkipsUnrecognisedRecordTypes() throws Exception {
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 1),
new byte[123]);
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 2),
new byte[0]);
byte[] ack = createAck(false);
ByteArrayOutputStream input = new ByteArrayOutputStream();
input.write(skip1);
input.write(skip2);
input.write(ack);
ByteArrayInputStream in = new ByteArrayInputStream(input.toByteArray());
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
assertTrue(reader.hasAck());
Ack a = reader.readAck();
assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size());
}
private byte[] createAck(boolean tooBig) throws Exception {
return createRecord(PROTOCOL_VERSION, ACK, createPayload(tooBig));
}
private byte[] createEmptyAck() throws Exception {
return createRecord(PROTOCOL_VERSION, ACK, new byte[0]);
}
private byte[] createOffer(boolean tooBig) throws Exception {
return createRecord(PROTOCOL_VERSION, OFFER, createPayload(tooBig));
}
private byte[] createEmptyOffer() throws Exception {
return createRecord(PROTOCOL_VERSION, OFFER, new byte[0]);
}
private byte[] createRequest(boolean tooBig) throws Exception {
return createRecord(PROTOCOL_VERSION, REQUEST, createPayload(tooBig));
}
private byte[] createEmptyRequest() throws Exception {
return createRecord(PROTOCOL_VERSION, REQUEST, new byte[0]);
}
private byte[] createRecord(byte version, byte type, byte[] payload) {
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length];
b[0] = version;
b[1] = type;
ByteUtils.writeUint16(payload.length, b, 2);
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
return b;
}
private byte[] createPayload(boolean tooBig) throws Exception {
ByteArrayOutputStream payload = new ByteArrayOutputStream();
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_LENGTH) {
payload.write(TestUtils.getRandomId());
}
if (tooBig) payload.write(TestUtils.getRandomId());
assertEquals(tooBig, payload.size() > MAX_RECORD_PAYLOAD_LENGTH);
return payload.toByteArray();
}
}

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.RecordWriter; import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
@@ -29,14 +29,14 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
private final ContactId contactId; private final ContactId contactId;
private final MessageId messageId; private final MessageId messageId;
private final int maxLatency; private final int maxLatency;
private final RecordWriter recordWriter; private final SyncRecordWriter recordWriter;
public SimplexOutgoingSessionTest() { public SimplexOutgoingSessionTest() {
context = new Mockery(); context = new Mockery();
db = context.mock(DatabaseComponent.class); db = context.mock(DatabaseComponent.class);
dbExecutor = new ImmediateExecutor(); dbExecutor = new ImmediateExecutor();
eventBus = context.mock(EventBus.class); eventBus = context.mock(EventBus.class);
recordWriter = context.mock(RecordWriter.class); recordWriter = context.mock(SyncRecordWriter.class);
contactId = new ContactId(234); contactId = new ContactId(234);
messageId = new MessageId(TestUtils.getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
maxLatency = Integer.MAX_VALUE; maxLatency = Integer.MAX_VALUE;

View File

@@ -12,11 +12,11 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.RecordReader;
import org.briarproject.bramble.api.sync.RecordReaderFactory;
import org.briarproject.bramble.api.sync.RecordWriter;
import org.briarproject.bramble.api.sync.RecordWriterFactory;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory; import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.bramble.api.transport.StreamWriterFactory;
@@ -54,9 +54,9 @@ public class SyncIntegrationTest extends BrambleTestCase {
@Inject @Inject
StreamWriterFactory streamWriterFactory; StreamWriterFactory streamWriterFactory;
@Inject @Inject
RecordReaderFactory recordReaderFactory; SyncRecordReaderFactory recordReaderFactory;
@Inject @Inject
RecordWriterFactory recordWriterFactory; SyncRecordWriterFactory recordWriterFactory;
@Inject @Inject
TransportCrypto transportCrypto; TransportCrypto transportCrypto;
@@ -81,9 +81,9 @@ public class SyncIntegrationTest extends BrambleTestCase {
streamNumber = 123; streamNumber = 123;
// Create a group // Create a group
ClientId clientId = getClientId(); ClientId clientId = getClientId();
int clientVersion = 1234567890; int majorVersion = 1234567890;
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH]; byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
Group group = groupFactory.createGroup(clientId, clientVersion, Group group = groupFactory.createGroup(clientId, majorVersion,
descriptor); descriptor);
// Add two messages to the group // Add two messages to the group
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
@@ -104,7 +104,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
headerKey, streamNumber); headerKey, streamNumber);
OutputStream streamWriter = streamWriterFactory.createStreamWriter(out, OutputStream streamWriter = streamWriterFactory.createStreamWriter(out,
ctx); ctx);
RecordWriter recordWriter = recordWriterFactory.createRecordWriter( SyncRecordWriter recordWriter = recordWriterFactory.createRecordWriter(
streamWriter); streamWriter);
recordWriter.writeAck(new Ack(messageIds)); recordWriter.writeAck(new Ack(messageIds));
@@ -112,8 +112,8 @@ public class SyncIntegrationTest extends BrambleTestCase {
recordWriter.writeMessage(message1.getRaw()); recordWriter.writeMessage(message1.getRaw());
recordWriter.writeOffer(new Offer(messageIds)); recordWriter.writeOffer(new Offer(messageIds));
recordWriter.writeRequest(new Request(messageIds)); recordWriter.writeRequest(new Request(messageIds));
recordWriter.flush();
streamWriter.flush();
return out.toByteArray(); return out.toByteArray();
} }
@@ -134,7 +134,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
headerKey, streamNumber); headerKey, streamNumber);
InputStream streamReader = streamReaderFactory.createStreamReader(in, InputStream streamReader = streamReaderFactory.createStreamReader(in,
ctx); ctx);
RecordReader recordReader = recordReaderFactory.createRecordReader( SyncRecordReader recordReader = recordReaderFactory.createRecordReader(
streamReader); streamReader);
// Read the ack // Read the ack

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.crypto.CryptoModule;
import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.test.TestSecureRandomModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
@@ -13,6 +14,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
TestSecureRandomModule.class, TestSecureRandomModule.class,
CryptoModule.class, CryptoModule.class,
RecordModule.class,
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,
TransportModule.class TransportModule.class

View File

@@ -0,0 +1,195 @@
package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.record.Record;
import org.briarproject.bramble.api.record.RecordReader;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SyncRecordReaderImplTest extends BrambleMockTestCase {
private final MessageFactory messageFactory =
context.mock(MessageFactory.class);
private final RecordReader recordReader = context.mock(RecordReader.class);
@Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
expectReadRecord(createAck());
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
Ack ack = reader.readAck();
assertEquals(MAX_MESSAGE_IDS, ack.getMessageIds().size());
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfAckIsEmpty() throws Exception {
expectReadRecord(createEmptyAck());
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
reader.readAck();
}
@Test
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
expectReadRecord(createOffer());
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
Offer offer = reader.readOffer();
assertEquals(MAX_MESSAGE_IDS, offer.getMessageIds().size());
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
expectReadRecord(createEmptyOffer());
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
reader.readOffer();
}
@Test
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
expectReadRecord(createRequest());
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
Request request = reader.readRequest();
assertEquals(MAX_MESSAGE_IDS, request.getMessageIds().size());
}
@Test(expected = FormatException.class)
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
expectReadRecord(createEmptyRequest());
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
reader.readRequest();
}
@Test
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(throwException(new EOFException()));
}});
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
assertTrue(reader.eof());
assertTrue(reader.eof());
}
@Test
public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception {
expectReadRecord(createAck());
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
assertFalse(reader.eof());
assertFalse(reader.eof());
}
@Test(expected = FormatException.class)
public void testThrowsExceptionIfProtocolVersionIsUnrecognised()
throws Exception {
byte version = (byte) (PROTOCOL_VERSION + 1);
byte[] payload = getRandomId();
expectReadRecord(new Record(version, ACK, payload));
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
reader.eof();
}
@Test
public void testSkipsUnrecognisedRecordTypes() throws Exception {
byte type1 = (byte) (REQUEST + 1);
byte[] payload1 = getRandomBytes(123);
Record unknownRecord1 = new Record(PROTOCOL_VERSION, type1, payload1);
byte type2 = (byte) (REQUEST + 2);
byte[] payload2 = new byte[0];
Record unknownRecord2 = new Record(PROTOCOL_VERSION, type2, payload2);
Record ackRecord = createAck();
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(unknownRecord1));
oneOf(recordReader).readRecord();
will(returnValue(unknownRecord2));
oneOf(recordReader).readRecord();
will(returnValue(ackRecord));
}});
SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
assertTrue(reader.hasAck());
Ack a = reader.readAck();
assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size());
}
private void expectReadRecord(Record record) throws Exception {
context.checking(new Expectations() {{
oneOf(recordReader).readRecord();
will(returnValue(record));
}});
}
private Record createAck() throws Exception {
return new Record(PROTOCOL_VERSION, ACK, createPayload());
}
private Record createEmptyAck() throws Exception {
return new Record(PROTOCOL_VERSION, ACK, new byte[0]);
}
private Record createOffer() throws Exception {
return new Record(PROTOCOL_VERSION, OFFER, createPayload());
}
private Record createEmptyOffer() throws Exception {
return new Record(PROTOCOL_VERSION, OFFER, new byte[0]);
}
private Record createRequest() throws Exception {
return new Record(PROTOCOL_VERSION, REQUEST, createPayload());
}
private Record createEmptyRequest() throws Exception {
return new Record(PROTOCOL_VERSION, REQUEST, new byte[0]);
}
private byte[] createPayload() throws Exception {
ByteArrayOutputStream payload = new ByteArrayOutputStream();
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_BYTES) {
payload.write(getRandomId());
}
return payload.toByteArray();
}
}

View File

@@ -53,10 +53,11 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
private final Executor dbExecutor = new ImmediateExecutor(); private final Executor dbExecutor = new ImmediateExecutor();
private final Executor validationExecutor = new ImmediateExecutor(); private final Executor validationExecutor = new ImmediateExecutor();
private final ClientId clientId = getClientId(); private final ClientId clientId = getClientId();
private final int majorVersion = 123;
private final MessageId messageId = new MessageId(getRandomId()); private final MessageId messageId = new MessageId(getRandomId());
private final MessageId messageId1 = new MessageId(getRandomId()); private final MessageId messageId1 = new MessageId(getRandomId());
private final MessageId messageId2 = new MessageId(getRandomId()); private final MessageId messageId2 = new MessageId(getRandomId());
private final Group group = getGroup(clientId); private final Group group = getGroup(clientId, majorVersion);
private final GroupId groupId = group.getId(); private final GroupId groupId = group.getId();
private final long timestamp = System.currentTimeMillis(); private final long timestamp = System.currentTimeMillis();
private final byte[] raw = new byte[123]; private final byte[] raw = new byte[123];
@@ -85,8 +86,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
public void setUp() { public void setUp() {
vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor, vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor,
messageFactory); messageFactory);
vm.registerMessageValidator(clientId, validator); vm.registerMessageValidator(clientId, majorVersion, validator);
vm.registerIncomingMessageHook(clientId, hook); vm.registerIncomingMessageHook(clientId, majorVersion, hook);
} }
@Test @Test

View File

@@ -0,0 +1,21 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import java.util.concurrent.Executor;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class TestCryptoExecutorModule {
@Provides
@Singleton
@CryptoExecutor
Executor provideCryptoExecutor() {
return new ImmediateExecutor();
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.test; package org.briarproject.bramble.test;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
@@ -11,6 +12,7 @@ import static org.briarproject.bramble.test.UTest.Result.LARGER;
import static org.briarproject.bramble.test.UTest.Result.SMALLER; import static org.briarproject.bramble.test.UTest.Result.SMALLER;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@Ignore
public class UTestTest extends BrambleTestCase { public class UTestTest extends BrambleTestCase {
private final Random random = new Random(); private final Random random = new Random();

View File

@@ -24,7 +24,7 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase {
context.mock(MetadataEncoder.class); context.mock(MetadataEncoder.class);
protected final Clock clock = context.mock(Clock.class); protected final Clock clock = context.mock(Clock.class);
protected final Group group = getGroup(getClientId()); protected final Group group = getGroup(getClientId(), 123);
protected final GroupId groupId = group.getId(); protected final GroupId groupId = group.getId();
protected final byte[] descriptor = group.getDescriptor(); protected final byte[] descriptor = group.getDescriptor();
protected final Message message = getMessage(groupId); protected final Message message = getMessage(groupId);

View File

@@ -0,0 +1,669 @@
package org.briarproject.bramble.versioning;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getClientId;
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.getRandomId;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertFalse;
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final ContactGroupFactory contactGroupFactory =
context.mock(ContactGroupFactory.class);
private final Clock clock = context.mock(Clock.class);
private final ClientVersioningHook hook =
context.mock(ClientVersioningHook.class);
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Contact contact = new Contact(new ContactId(123),
getAuthor(), getLocalAuthor().getId(), true, true);
private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis();
private final Transaction txn = new Transaction(null, false);
private ClientVersioningManagerImpl createInstance() throws Exception {
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
MAJOR_VERSION);
will(returnValue(localGroup));
}});
return new ClientVersioningManagerImpl(db, clientHelper,
contactGroupFactory, clock);
}
@Test
public void testCreatesGroupsAtStartup() throws Exception {
context.checking(new Expectations() {{
oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(false));
oneOf(db).addGroup(txn, localGroup);
oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact)));
}});
expectAddingContact();
ClientVersioningManagerImpl c = createInstance();
c.createLocalState(txn);
}
@Test
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
throws Exception {
context.checking(new Expectations() {{
oneOf(db).containsGroup(txn, localGroup.getId());
will(returnValue(true));
}});
ClientVersioningManagerImpl c = createInstance();
c.createLocalState(txn);
}
@Test
public void testCreatesContactGroupWhenAddingContact() throws Exception {
expectAddingContact();
ClientVersioningManagerImpl c = createInstance();
c.addingContact(txn, contact);
}
private void expectAddingContact() throws Exception {
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
long now = System.currentTimeMillis();
BdfList localUpdateBody = BdfList.of(new BdfList(), 1L);
Message localUpdate = getMessage(contactGroup.getId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED);
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
groupMeta);
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
localUpdateBody);
will(returnValue(localUpdate));
oneOf(clientHelper).addLocalMessage(txn, localUpdate,
localUpdateMeta, true);
}});
}
@Test
public void testRemovesGroupWhenRemovingContact() throws Exception {
context.checking(new Expectations() {{
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
oneOf(db).removeGroup(txn, contactGroup);
}});
ClientVersioningManagerImpl c = createInstance();
c.removingContact(txn, contact);
}
@Test
public void testStoresClientVersionsAtFirstStartup() throws Exception {
BdfList localVersionsBody =
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
Message localVersions = getMessage(localGroup.getId());
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfList localUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// No client versions have been stored yet
oneOf(db).getMessageIds(txn, localGroup.getId());
will(returnValue(emptyList()));
// Store the client versions
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(localGroup.getId(), now,
localVersionsBody);
will(returnValue(localVersions));
oneOf(db).addLocalMessage(txn, localVersions, new Metadata(),
false);
// Inform contacts that client versions have changed
oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
// Find the latest local and remote updates (no remote update)
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
will(returnValue(localUpdateBody));
// Latest local update is up-to-date, no visibilities have changed
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
c.startService();
}
@Test
public void testComparesClientVersionsAtSubsequentStartup()
throws Exception {
MessageId localVersionsId = new MessageId(getRandomId());
BdfList localVersionsBody =
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// Load the old client versions
oneOf(db).getMessageIds(txn, localGroup.getId());
will(returnValue(singletonList(localVersionsId)));
oneOf(clientHelper).getMessageAsList(txn, localVersionsId);
will(returnValue(localVersionsBody));
// Client versions are up-to-date
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
c.startService();
}
@Test
public void testStoresClientVersionsAtSubsequentStartupIfChanged()
throws Exception {
// The client had minor version 234 in the old client versions
BdfList oldLocalVersionsBody =
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
// The client has minor version 345 in the new client versions
BdfList newLocalVersionsBody =
BdfList.of(BdfList.of(clientId.getString(), 123, 345));
// The client had minor version 234 in the old local update
BdfList oldLocalUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
// The client has minor version 345 in the new local update
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 345, false)), 2L);
MessageId oldLocalVersionsId = new MessageId(getRandomId());
Message newLocalVersions = getMessage(localGroup.getId());
MessageId oldLocalUpdateId = new MessageId(getRandomId());
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
Message newLocalUpdate = getMessage(contactGroup.getId());
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// Load the old client versions
oneOf(db).getMessageIds(txn, localGroup.getId());
will(returnValue(singletonList(oldLocalVersionsId)));
oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId);
will(returnValue(oldLocalVersionsBody));
// Delete the old client versions
oneOf(db).removeMessage(txn, oldLocalVersionsId);
// Store the new client versions
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(localGroup.getId(), now,
newLocalVersionsBody);
will(returnValue(newLocalVersions));
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
false);
// Inform contacts that client versions have changed
oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
// Find the latest local and remote updates (no remote update)
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(singletonMap(oldLocalUpdateId,
oldLocalUpdateMeta)));
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Delete the latest local update
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
// Store the new local update
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
// No visibilities have changed
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 345, hook);
c.startService();
}
@Test
public void testActivatesNewClientAtStartupIfAlreadyAdvertisedByContact()
throws Exception {
testActivatesNewClientAtStartup(false, VISIBLE);
}
@Test
public void testActivatesNewClientAtStartupIfAlreadyActivatedByContact()
throws Exception {
testActivatesNewClientAtStartup(true, SHARED);
}
private void testActivatesNewClientAtStartup(boolean remoteActive,
Visibility visibility) throws Exception {
// The client was missing from the old client versions
BdfList oldLocalVersionsBody = new BdfList();
// The client is included in the new client versions
BdfList newLocalVersionsBody =
BdfList.of(BdfList.of(clientId.getString(), 123, 234));
// The client was missing from the old local update
BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L);
// The client was included in the old remote update
BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 345, remoteActive)), 1L);
// The client is active in the new local update
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, true)), 2L);
MessageId oldLocalVersionsId = new MessageId(getRandomId());
Message newLocalVersions = getMessage(localGroup.getId());
MessageId oldLocalUpdateId = new MessageId(getRandomId());
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
Message newLocalUpdate = getMessage(localGroup.getId());
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// Load the old client versions
oneOf(db).getMessageIds(txn, localGroup.getId());
will(returnValue(singletonList(oldLocalVersionsId)));
oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId);
will(returnValue(oldLocalVersionsBody));
// Delete the old client versions
oneOf(db).removeMessage(txn, oldLocalVersionsId);
// Store the new client versions
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(localGroup.getId(), now,
newLocalVersionsBody);
will(returnValue(newLocalVersions));
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
false);
// Inform contacts that client versions have changed
oneOf(db).getContacts(txn);
will(returnValue(singletonList(contact)));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
// Find the latest local and remote updates
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Load the latest remote update
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
will(returnValue(oldRemoteUpdateBody));
// Delete the latest local update
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
// Store the new local update
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
// The client's visibility has changed
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
c.startService();
}
@Test
public void testDeletesObsoleteRemoteUpdate() throws Exception {
Message newRemoteUpdate = getMessage(contactGroup.getId());
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
MessageId oldLocalUpdateId = new MessageId(getRandomId());
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
will(returnValue(newRemoteUpdateBody));
// Find the latest local and remote updates
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
// Delete the new remote update, which is obsolete
oneOf(db).deleteMessage(txn, newRemoteUpdate.getId());
oneOf(db).deleteMessageMetadata(txn, newRemoteUpdate.getId());
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
}
@Test
public void testDeletesPreviousRemoteUpdate() throws Exception {
Message newRemoteUpdate = getMessage(contactGroup.getId());
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L);
MessageId oldLocalUpdateId = new MessageId(getRandomId());
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L);
BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
will(returnValue(newRemoteUpdateBody));
// Find the latest local and remote updates
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Load the latest remote update
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
will(returnValue(oldRemoteUpdateBody));
// Delete the old remote update
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// No states or visibilities have changed
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
}
@Test
public void testAcceptsFirstRemoteUpdate() throws Exception {
Message newRemoteUpdate = getMessage(contactGroup.getId());
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
MessageId oldLocalUpdateId = new MessageId(getRandomId());
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L);
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
will(returnValue(newRemoteUpdateBody));
// Find the latest local and remote updates (no remote update)
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(singletonMap(oldLocalUpdateId,
oldLocalUpdateMeta)));
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// No states or visibilities have changed
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
}
@Test
public void testActivatesClientOnIncomingMessageWhenAdvertisedByContact()
throws Exception {
testActivatesClientOnIncomingMessage(false, VISIBLE);
}
@Test
public void testActivatesClientOnIncomingMessageWhenActivatedByContact()
throws Exception {
testActivatesClientOnIncomingMessage(true, SHARED);
}
private void testActivatesClientOnIncomingMessage(boolean remoteActive,
Visibility visibility) throws Exception {
// The client was missing from the old remote update
BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L);
// The client was inactive in the old local update
BdfList oldLocalUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
// The client is included in the new remote update
BdfList newRemoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, remoteActive)), 2L);
// The client is active in the new local update
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, true)), 2L);
Message newRemoteUpdate = getMessage(contactGroup.getId());
MessageId oldLocalUpdateId = new MessageId(getRandomId());
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
Message newLocalUpdate = getMessage(contactGroup.getId());
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
will(returnValue(newRemoteUpdateBody));
// Find the latest local and remote updates
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Load the latest remote update
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
will(returnValue(oldRemoteUpdateBody));
// Delete the old remote update
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Delete the old local update
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
// Store the new local update
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
// The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
}
@Test
public void testDeactivatesClientOnIncomingMessage() throws Exception {
// The client was active in the old local and remote updates
BdfList oldLocalUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
// The client is missing from the new remote update
BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L);
// The client is inactive in the new local update
BdfList newLocalUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 2L);
Message newRemoteUpdate = getMessage(contactGroup.getId());
MessageId oldLocalUpdateId = new MessageId(getRandomId());
BdfDictionary oldLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId oldRemoteUpdateId = new MessageId(getRandomId());
BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta);
messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta);
Message newLocalUpdate = getMessage(contactGroup.getId());
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
will(returnValue(newRemoteUpdateBody));
// Find the latest local and remote updates
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Load the latest remote update
oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId);
will(returnValue(oldRemoteUpdateBody));
// Delete the old remote update
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Delete the old local update
oneOf(db).deleteMessage(txn, oldLocalUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId);
// Store the new local update
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
newLocalUpdateBody);
will(returnValue(newLocalUpdate));
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true);
// The client's visibility has changed
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);
}});
ClientVersioningManagerImpl c = createInstance();
c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
}
}

View File

@@ -0,0 +1,274 @@
package org.briarproject.bramble.versioning;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.BdfMessageContext;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.junit.Test;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_LENGTH;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID;
import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION;
import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertEquals;
public class ClientVersioningValidatorTest extends BrambleMockTestCase {
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final MetadataEncoder metadataEncoder =
context.mock(MetadataEncoder.class);
private final Clock clock = context.mock(Clock.class);
private final ClientVersioningValidator validator =
new ClientVersioningValidator(clientHelper, metadataEncoder, clock);
private final Group group = getGroup(CLIENT_ID, MAJOR_VERSION);
private final Message message = getMessage(group.getId());
private final ClientId clientId = getClientId();
@Test(expected = FormatException.class)
public void testRejectsTooShortBody() throws Exception {
BdfList body = BdfList.of(new BdfList());
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongBody() throws Exception {
BdfList body = BdfList.of(new BdfList(), 123, null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNullStatesList() throws Exception {
BdfList body = BdfList.of(null, 123);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNonListStatesList() throws Exception {
BdfList body = BdfList.of("", 123);
validator.validateMessage(message, group, body);
}
@Test
public void testAcceptsEmptyStatesList() throws Exception {
BdfList body = BdfList.of(new BdfList(), 123);
BdfMessageContext context =
validator.validateMessage(message, group, body);
assertEquals(emptyList(), context.getDependencies());
BdfDictionary expectedMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 123L),
new BdfEntry(MSG_KEY_LOCAL, false));
assertEquals(expectedMeta, context.getDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsNullUpdateVersion() throws Exception {
BdfList body = BdfList.of(new BdfList(), null);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNonLongUpdateVersion() throws Exception {
BdfList body = BdfList.of(new BdfList(), "123");
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNegativeUpdateVersion() throws Exception {
BdfList body = BdfList.of(new BdfList(), -1);
validator.validateMessage(message, group, body);
}
@Test
public void testAcceptsZeroUpdateVersion() throws Exception {
BdfList body = BdfList.of(new BdfList(), 0);
BdfMessageContext context =
validator.validateMessage(message, group, body);
assertEquals(emptyList(), context.getDependencies());
BdfDictionary expectedMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 0L),
new BdfEntry(MSG_KEY_LOCAL, false));
assertEquals(expectedMeta, context.getDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsTooShortClientState() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, 234);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongClientState() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, 234, true, null);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNullClientId() throws Exception {
BdfList state = BdfList.of(null, 123, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNonStringClientId() throws Exception {
byte[] id = getRandomBytes(MAX_CLIENT_ID_LENGTH);
BdfList state = BdfList.of(id, 123, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsTooShortClientId() throws Exception {
BdfList state = BdfList.of("", 123, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test
public void testAcceptsMinLengthClientId() throws Exception {
BdfList state = BdfList.of(getRandomString(1), 123, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
BdfMessageContext context =
validator.validateMessage(message, group, body);
assertEquals(emptyList(), context.getDependencies());
BdfDictionary expectedMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
new BdfEntry(MSG_KEY_LOCAL, false));
assertEquals(expectedMeta, context.getDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsTooLongClientId() throws Exception {
String id = getRandomString(MAX_CLIENT_ID_LENGTH + 1);
BdfList state = BdfList.of(id, 123, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test
public void testAcceptsMaxLengthClientId() throws Exception {
String id = getRandomString(MAX_CLIENT_ID_LENGTH);
BdfList state = BdfList.of(id, 123, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
BdfMessageContext context =
validator.validateMessage(message, group, body);
assertEquals(emptyList(), context.getDependencies());
BdfDictionary expectedMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
new BdfEntry(MSG_KEY_LOCAL, false));
assertEquals(expectedMeta, context.getDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsNullMajorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), null, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNonLongMajorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), "123", 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNegativeMajorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), -1, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test
public void testAcceptsZeroMajorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 0, 234, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
BdfMessageContext context =
validator.validateMessage(message, group, body);
assertEquals(emptyList(), context.getDependencies());
BdfDictionary expectedMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
new BdfEntry(MSG_KEY_LOCAL, false));
assertEquals(expectedMeta, context.getDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsNullMinorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, null, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNonLongMinorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, "234", true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNegativeMinorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, -1, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test
public void testAcceptsZeroMinorVersion() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, 0, true);
BdfList body = BdfList.of(BdfList.of(state), 345);
BdfMessageContext context =
validator.validateMessage(message, group, body);
assertEquals(emptyList(), context.getDependencies());
BdfDictionary expectedMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
new BdfEntry(MSG_KEY_LOCAL, false));
assertEquals(expectedMeta, context.getDictionary());
}
@Test(expected = FormatException.class)
public void testRejectsNullActiveFlag() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, 234, null);
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test(expected = FormatException.class)
public void testRejectsNonBooleanActiveFlag() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, 234, "true");
BdfList body = BdfList.of(BdfList.of(state), 345);
validator.validateMessage(message, group, body);
}
@Test
public void testAcceptsNegativeActiveFlag() throws Exception {
BdfList state = BdfList.of(clientId.getString(), 123, 234, false);
BdfList body = BdfList.of(BdfList.of(state), 345);
BdfMessageContext context =
validator.validateMessage(message, group, body);
assertEquals(emptyList(), context.getDependencies());
BdfDictionary expectedMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 345L),
new BdfEntry(MSG_KEY_LOCAL, false));
assertEquals(expectedMeta, context.getDictionary());
}
}

View File

@@ -27,22 +27,17 @@ dependencies {
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.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3', 'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b', 'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9', 'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99', 'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'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',
'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',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'net.java.dev.jna:jna-platform:4.4.0:jna-platform-4.4.0.jar:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499', 'net.java.dev.jna:jna-platform:4.4.0:jna-platform-4.4.0.jar:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
'net.java.dev.jna:jna:4.4.0:jna-4.4.0.jar:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1', 'net.java.dev.jna:jna:4.4.0:jna-4.4.0.jar:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
'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.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c', 'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',

View File

@@ -31,9 +31,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
// Non-null if the plugin started successfully // Non-null if the plugin started successfully
private volatile LocalDevice localDevice = null; private volatile LocalDevice localDevice = null;
JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) { Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, callback, maxLatency); super(connectionManager, ioExecutor, secureRandom, backoff, callback,
maxLatency);
} }
@Override @Override
@@ -110,6 +112,6 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
} }
private DuplexTransportConnection wrapSocket(StreamConnection s) { private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new JavaBluetoothTransportConnection(this, s); return new JavaBluetoothTransportConnection(this, connectionLimiter, s);
} }
} }

View File

@@ -51,10 +51,12 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
@Override @Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor, JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
secureRandom, backoff, callback, MAX_LATENCY); ioExecutor, secureRandom, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -14,11 +14,15 @@ import javax.microedition.io.StreamConnection;
class JavaBluetoothTransportConnection class JavaBluetoothTransportConnection
extends AbstractDuplexTransportConnection { extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionManager;
private final StreamConnection stream; private final StreamConnection stream;
JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) { JavaBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionManager,
StreamConnection stream) {
super(plugin); super(plugin);
this.stream = stream; this.stream = stream;
this.connectionManager = connectionManager;
} }
@Override @Override
@@ -33,6 +37,10 @@ class JavaBluetoothTransportConnection
@Override @Override
protected void closeConnection(boolean exception) throws IOException { protected void closeConnection(boolean exception) throws IOException {
stream.close(); try {
stream.close();
} finally {
connectionManager.connectionClosed(this);
}
} }
} }

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