Compare commits

..

2 Commits

Author SHA1 Message Date
akwizgran
21d6dfe817 Add transactional helper methods to DbViewModel. 2021-01-26 14:52:25 +00:00
akwizgran
c1e83b22c1 Add helper method for running a DB task and logging any exception it throws. 2021-01-26 13:51:03 +00:00
732 changed files with 9556 additions and 27572 deletions

1
.gitignore vendored
View File

@@ -18,7 +18,6 @@ local.properties
# Android Studio # Android Studio
.idea/* .idea/*
!.idea/inspectionProfiles/
!.idea/runConfigurations/ !.idea/runConfigurations/
!.idea/codeStyleSettings.xml !.idea/codeStyleSettings.xml
!.idea/codeStyles !.idea/codeStyles

View File

@@ -1,79 +1,29 @@
image: briar/ci-image-android:latest image: briar/ci-image-android:latest
stages: stages:
- test - test
- optional_tests - check_reproducibility
- check_reproducibility
workflow: test:
# when to create a CI pipeline stage: test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
when: never # avoids duplicate jobs for branch and MR
- if: '$CI_COMMIT_BRANCH'
- if: '$CI_COMMIT_TAG'
.base-test:
before_script: before_script:
- set -e - set -e
- export GRADLE_USER_HOME=$PWD/.gradle - export GRADLE_USER_HOME=$PWD/.gradle
cache: cache:
key: "$CI_COMMIT_REF_SLUG"
paths: paths:
- .gradle/wrapper - .gradle/wrapper
- .gradle/caches - .gradle/caches
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources
after_script: after_script:
# these file change every time and should not be cached # these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock - rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/ - rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
test:
extends: .base-test
stage: test
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
- when: always
android test:
extends: .base-test
stage: optional_tests
image: briar/ci-image-android-emulator:latest
script:
# start emulator first, so it can fail early
- start-emulator.sh
# run normal and screenshot tests together (exclude Large tests)
- ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.package=org.briarproject.briar.android -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest
after_script:
- adb pull /sdcard/Pictures/screenshots
artifacts:
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
paths:
- kernel.log
- logcat.txt
- briar-android/build/reports/androidTests/connected/flavors/*
- screenshots
expire_in: 3 days
when: on_failure
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- briar-android/**/*
when: manual
allow_failure: true
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
retry:
max: 1
tags:
- kvm
test_reproducible: test_reproducible:
stage: check_reproducibility stage: check_reproducibility
@@ -81,38 +31,3 @@ test_reproducible:
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline" - "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
only: only:
- tags - tags
.optional_tests:
stage: optional_tests
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
after_script:
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
bridge test:
extends: .optional_tests
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: on_success
allow_failure: true
- if: '$CI_COMMIT_TAG == null'
when: manual
allow_failure: true
pre_release_tests:
extends: .optional_tests
only:
- tags

View File

@@ -1,15 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="briar">
<words>
<w>briar</w>
<w>briarproject</w>
<w>emoji</w>
<w>encrypter</w>
<w>identicon</w>
<w>introducee</w>
<w>introducees</w>
<w>introducer</w>
<w>onboarding</w>
</words>
</dictionary>
</component>

View File

@@ -1,14 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
<inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
<option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="true" />
<option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="true" />
<option name="SUGGEST_PRIVATE_FOR_INNERS" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
<module name="briar.bramble-java" />
<useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
<envs>
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</envs>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -1,51 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Instrumentation Tests (destroys DB)" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="briar.briar-android" />
<option name="TESTING_TYPE" value="1" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="org.briarproject.briar.android" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="-e notAnnotation androidx.test.filters.LargeTest" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Hybrid>
<Java />
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -6,17 +6,13 @@ apply from: 'witness.gradle'
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.3' buildToolsVersion '30.0.2'
packagingOptions {
doNotStrip '**/*.so'
}
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 29
versionCode 10303 versionCode 10213
versionName "1.3.3" versionName "1.2.13"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -42,8 +38,8 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.13@zip' tor 'org.briarproject:tor-android:0.3.5.12@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip' tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -59,8 +59,8 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class AndroidBluetoothPlugin extends class AndroidBluetoothPlugin
AbstractBluetoothPlugin<BluetoothSocket, BluetoothServerSocket> { extends BluetoothPlugin<BluetoothSocket, BluetoothServerSocket> {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidBluetoothPlugin.class.getName()); getLogger(AndroidBluetoothPlugin.class.getName());
@@ -75,7 +75,6 @@ class AndroidBluetoothPlugin extends
// Non-null if the plugin started successfully // Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null; private volatile BluetoothAdapter adapter = null;
private volatile boolean stopDiscoverAndConnect;
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
BluetoothConnectionFactory<BluetoothSocket> connectionFactory, BluetoothConnectionFactory<BluetoothSocket> connectionFactory,
@@ -176,11 +175,6 @@ class AndroidBluetoothPlugin extends
} catch (IOException e) { } catch (IOException e) {
IoUtils.tryToClose(s, LOG, WARNING); IoUtils.tryToClose(s, LOG, WARNING);
throw e; throw e;
} catch (NullPointerException e) {
// BluetoothSocket#connect() may throw an NPE under unknown
// circumstances
IoUtils.tryToClose(s, LOG, WARNING);
throw new IOException(e);
} }
} }
@@ -188,40 +182,22 @@ class AndroidBluetoothPlugin extends
@Nullable @Nullable
DuplexTransportConnection discoverAndConnect(String uuid) { DuplexTransportConnection discoverAndConnect(String uuid) {
if (adapter == null) return null; if (adapter == null) return null;
if (!discoverSemaphore.tryAcquire()) { for (String address : discoverDevices()) {
LOG.info("Discover already running"); try {
return null; if (LOG.isLoggable(INFO))
} LOG.info("Connecting to " + scrubMacAddress(address));
try { return connectTo(address, uuid);
stopDiscoverAndConnect = false; } catch (IOException e) {
for (String address : discoverDevices()) { if (LOG.isLoggable(INFO)) {
if (stopDiscoverAndConnect) { LOG.info("Could not connect to "
break; + scrubMacAddress(address));
}
try {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
return connectTo(address, uuid);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to "
+ scrubMacAddress(address));
}
} }
} }
} finally {
discoverSemaphore.release();
} }
LOG.info("Could not connect to any devices"); LOG.info("Could not connect to any devices");
return null; return null;
} }
@Override
public void stopDiscoverAndConnect() {
stopDiscoverAndConnect = true;
adapter.cancelDiscovery();
}
private Collection<String> discoverDevices() { private Collection<String> discoverDevices() {
List<String> addresses = new ArrayList<>(); List<String> addresses = new ArrayList<>();
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>(); BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();

View File

@@ -47,7 +47,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
@Inject @Inject
AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor, public AndroidBluetoothPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
AndroidWakeLockManager wakeLockManager, AndroidWakeLockManager wakeLockManager,

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_URI;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class AndroidRemovableDrivePlugin extends RemovableDrivePlugin {
private final Application app;
AndroidRemovableDrivePlugin(Application app, int maxLatency) {
super(maxLatency);
this.app = app;
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openInputStream(Uri.parse(uri));
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String uri = p.get(PROP_URI);
if (isNullOrEmpty(uri)) throw new IllegalArgumentException();
return app.getContentResolver().openOutputStream(Uri.parse(uri));
}
}

View File

@@ -1,47 +0,0 @@
package org.briarproject.bramble.plugin.file;
import android.app.Application;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class AndroidRemovableDrivePluginFactory implements
SimplexPluginFactory {
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
private final Application app;
@Inject
AndroidRemovableDrivePluginFactory(Application app) {
this.app = app;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new AndroidRemovableDrivePlugin(app, MAX_LATENCY);
}
}

View File

@@ -37,7 +37,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private final Application app; private final Application app;
@Inject @Inject
AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor, public AndroidLanTcpPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
EventBus eventBus, EventBus eventBus,
BackoffFactory backoffFactory, BackoffFactory backoffFactory,

View File

@@ -58,7 +58,7 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory {
private final File torDirectory; private final File torDirectory;
@Inject @Inject
AndroidTorPluginFactory(@IoExecutor Executor ioExecutor, public AndroidTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor, @WakefulIoExecutor Executor wakefulIoExecutor,
Application app, Application app,
NetworkManager networkManager, NetworkManager networkManager,

View File

@@ -31,7 +31,6 @@ public class AndroidUtils {
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00"; private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
private static final String STORED_REPORTS = "dev-reports"; private static final String STORED_REPORTS = "dev-reports";
private static final String STORED_LOGCAT = "dev-logcat";
public static Collection<String> getSupportedArchitectures() { public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>(); List<String> abis = new ArrayList<>();
@@ -108,14 +107,14 @@ public class AndroidUtils {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE); return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
} }
public static File getLogcatFile(Context ctx) {
return new File(ctx.getFilesDir(), STORED_LOGCAT);
}
/** /**
* Returns an array of supported content types for image attachments. * Returns an array of supported content types for image attachments.
* GIFs can't be compressed on API < 24 so they're not supported.
* <p>
* TODO: Remove this restriction when large message support is added
*/ */
public static String[] getSupportedImageContentTypes() { public static String[] getSupportedImageContentTypes() {
return new String[] {"image/jpeg", "image/png", "image/gif"}; if (SDK_INT < 24) return new String[] {"image/jpeg", "image/png"};
else return new String[] {"image/jpeg", "image/png", "image/gif"};
} }
} }

View File

@@ -75,8 +75,8 @@ dependencyVerification {
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82', 'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.13:tor-android-0.3.5.13.zip:e0978db136731dae07774b722970cdae1e462fb5adc82845dd80a7e2d87f356c', 'org.briarproject:tor-android:0.3.5.12:tor-android-0.3.5.12.zip:db71fb3290acff79d572af0752570eaf6aad7c4d88c9b9aa0b4d5afe2b9ead9c',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df', 'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface Consumer<T> {
void accept(T t);
}

View File

@@ -9,7 +9,4 @@ public interface FeatureFlags {
boolean shouldEnableProfilePictures(); boolean shouldEnableProfilePictures();
boolean shouldEnableDisappearingMessages();
boolean shouldEnableConnectViaBluetooth();
} }

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble.api;
public interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.bramble.api.cleanup;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.Collection;
/**
* An interface for registering a hook with the {@link CleanupManager}
* that will be called when a message's cleanup deadline is reached.
*/
@NotNullByDefault
public interface CleanupHook {
/**
* Called when the cleanup deadlines of one or more messages are reached.
* <p>
* The callee is not required to delete the messages, but the hook won't be
* called again for these messages unless another cleanup timer is set (see
* {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)}
* and {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)}).
*/
void deleteMessages(Transaction txn, GroupId g,
Collection<MessageId> messageIds) throws DbException;
}

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.api.cleanup;
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.MessageId;
/**
* The CleanupManager is responsible for tracking the cleanup deadlines of
* messages and passing them to their respective
* {@link CleanupHook CleanupHooks} when the deadlines are reached.
* <p>
* The CleanupManager responds to
* {@link CleanupTimerStartedEvent CleanupTimerStartedEvents} broadcast by the
* {@link DatabaseComponent}.
* <p>
* See {@link DatabaseComponent#setCleanupTimerDuration(Transaction, MessageId, long)},
* {@link DatabaseComponent#startCleanupTimer(Transaction, MessageId)},
* {@link DatabaseComponent#stopCleanupTimer(Transaction, MessageId)}.
*/
@NotNullByDefault
public interface CleanupManager {
/**
* When scheduling a cleanup task we overshoot the deadline by this many
* milliseconds to reduce the number of tasks that need to be scheduled
* when messages have cleanup deadlines that are close together.
*/
long BATCH_DELAY_MS = 1000;
/**
* Registers a hook to be called when messages are due for cleanup.
* This method should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerCleanupHook(ClientId c, int majorVersion,
CleanupHook hook);
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.bramble.api.cleanup.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a message's cleanup timer is started.
*/
@Immutable
@NotNullByDefault
public class CleanupTimerStartedEvent extends Event {
private final MessageId messageId;
private final long cleanupDeadline;
public CleanupTimerStartedEvent(MessageId messageId,
long cleanupDeadline) {
this.messageId = messageId;
this.cleanupDeadline = cleanupDeadline;
}
public MessageId getMessageId() {
return messageId;
}
public long getCleanupDeadline() {
return cleanupDeadline;
}
}

View File

@@ -32,31 +32,28 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* <p>
* If an unexpected exception occurs while handling data that is assumed
* to be valid (e.g. locally created metadata), it may be sensible to
* rethrow the unexpected exception as a DbException so that delivery is
* attempted again at next startup. This will allow delivery to succeed if
* the unexpected exception was caused by a bug that has subsequently been
* fixed.
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @throws DbException if a database error occurs while delivering the * @return Whether or not this message should be shared
* message. Delivery will be attempted again at next startup. Throwing * @throws DbException Should only be used for real database errors.
* this exception has the same effect as returning * If this is thrown, delivery will be attempted again at next startup,
* {@link DeliveryAction#DEFER}. * whereas if a FormatException is thrown, the message will be permanently
* @throws FormatException if the message is invalid in the context of its * invalidated.
* dependencies. The message and any dependents will be marked as invalid * @throws FormatException Use this for any non-database error
* and deleted along with their metadata. Throwing this exception has the * that occurs while handling remotely created data.
* same effect as returning {@link DeliveryAction#REJECT}. * This includes errors that occur while handling locally created data
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Never rethrow DbException as FormatException!
*/ */
protected abstract DeliveryAction incomingMessage(Transaction txn, protected abstract boolean incomingMessage(Transaction txn, Message m,
Message m, BdfList body, BdfDictionary meta) BdfList body, BdfDictionary meta) throws DbException,
throws DbException, FormatException; FormatException;
@Override @Override
public DeliveryAction incomingMessage(Transaction txn, Message m, public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
Metadata meta) throws DbException, InvalidMessageException { throws DbException, InvalidMessageException {
try { try {
BdfList body = clientHelper.toList(m); BdfList body = clientHelper.toList(m);
BdfDictionary metaDictionary = metadataParser.parse(meta); BdfDictionary metaDictionary = metadataParser.parse(meta);

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.client; package org.briarproject.bramble.api.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
@@ -17,7 +16,6 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Map; import java.util.Map;
@NotNullByDefault @NotNullByDefault
@@ -52,11 +50,9 @@ public interface ClientHelper {
BdfDictionary getGroupMetadataAsDictionary(Transaction txn, GroupId g) BdfDictionary getGroupMetadataAsDictionary(Transaction txn, GroupId g)
throws DbException, FormatException; throws DbException, FormatException;
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
BdfDictionary query) throws DbException, FormatException;
BdfDictionary getMessageMetadataAsDictionary(MessageId m) BdfDictionary getMessageMetadataAsDictionary(MessageId m)
throws DbException, FormatException; throws DbException,
FormatException;
BdfDictionary getMessageMetadataAsDictionary(Transaction txn, MessageId m) BdfDictionary getMessageMetadataAsDictionary(Transaction txn, MessageId m)
throws DbException, FormatException; throws DbException, FormatException;
@@ -123,17 +119,4 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap( Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
BdfDictionary properties) throws FormatException; BdfDictionary properties) throws FormatException;
/**
* Retrieves the contact ID from the group metadata of the given contact
* group.
*/
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException;
/**
* Stores the given contact ID in the group metadata of the given contact
* group.
*/
void setContactId(Transaction txn, GroupId contactGroupId, ContactId c)
throws DbException;
} }

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api.client;
public interface ContactGroupConstants {
/**
* Group metadata key for associating a contact ID with a contact group.
*/
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -1,35 +0,0 @@
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when the alias for a contact changed.
*/
@Immutable
@NotNullByDefault
public class ContactAliasChangedEvent extends Event {
private final ContactId contactId;
@Nullable
private final String alias;
public ContactAliasChangedEvent(ContactId contactId,
@Nullable String alias) {
this.contactId = contactId;
this.alias = alias;
}
public ContactId getContactId() {
return contactId;
}
@Nullable
public String getAlias() {
return alias;
}
}

View File

@@ -19,10 +19,4 @@ public interface StreamDecrypterFactory {
*/ */
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in, StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates a {@link StreamDecrypter} for decrypting a log stream.
*/
StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey);
} }

View File

@@ -17,12 +17,6 @@ public interface StreamEncrypterFactory {
* Creates a {@link StreamEncrypter} for encrypting a contact exchange * Creates a {@link StreamEncrypter} for encrypting a contact exchange
* stream. * stream.
*/ */
StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out, StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
SecretKey headerKey);
/**
* Creates a {@link StreamEncrypter} for encrypting a log stream.
*/
StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
} }

View File

@@ -41,18 +41,6 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface DatabaseComponent extends TransactionManager { public interface DatabaseComponent extends TransactionManager {
/**
* Return value for {@link #getNextCleanupDeadline(Transaction)} if
* no messages are scheduled to be deleted.
*/
long NO_CLEANUP_DEADLINE = -1;
/**
* Return value for {@link #startCleanupTimer(Transaction, MessageId)}
* if the cleanup timer was not started.
*/
long TIMER_NOT_STARTED = -1;
/** /**
* Opens the database and returns true if the database already existed. * Opens the database and returns true if the database already existed.
* *
@@ -118,18 +106,6 @@ public interface DatabaseComponent extends TransactionManager {
KeySetId addTransportKeys(Transaction txn, PendingContactId p, KeySetId addTransportKeys(Transaction txn, PendingContactId p,
TransportKeys k) throws DbException; TransportKeys k) throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(Transaction txn, ContactId c,
int maxLatency, boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.
@@ -162,16 +138,6 @@ public interface DatabaseComponent extends TransactionManager {
boolean containsPendingContact(Transaction txn, PendingContactId p) boolean containsPendingContact(Transaction txn, PendingContactId p)
throws DbException; throws DbException;
/**
* Returns true if the database contains keys for communicating with the
* given contact over the given transport. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
boolean containsTransportKeys(Transaction txn, ContactId c, TransportId t)
throws DbException;
/** /**
* Deletes the message with the given ID. Unlike * Deletes the message with the given ID. Unlike
* {@link #removeMessage(Transaction, MessageId)}, the message ID, * {@link #removeMessage(Transaction, MessageId)}, the message ID,
@@ -202,18 +168,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<Message> generateBatch(Transaction txn, ContactId c, Collection<Message> generateBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
/**
* Returns a batch of messages for the given contact containing the
* messages with the given IDs, for transmission over a transport with
* the given maximum latency.
* <p/>
* If any of the given messages are not in the database or are not visible
* to the contact, they are omitted from the batch without throwing an
* exception.
*/
Collection<Message> generateBatch(Transaction txn, ContactId c,
Collection<MessageId> ids, int maxLatency) throws DbException;
/** /**
* Returns an offer for the given contact for transmission over a * Returns an offer for the given contact for transmission over a
* transport with the given maximum latency, or null if there are no * transport with the given maximum latency, or null if there are no
@@ -334,16 +288,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessageIds(Transaction txn, GroupId g) Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
throws DbException; throws DbException;
/**
* Returns the IDs of any delivered messages in the given group with
* metadata that matches all entries in the given query. If the query is
* empty, the IDs of all delivered messages are returned.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
Metadata query) 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/>
@@ -370,15 +314,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<MessageId> getMessagesToShare(Transaction txn) Collection<MessageId> getMessagesToShare(Transaction txn)
throws DbException; throws DbException;
/**
* Returns the IDs of any messages of any messages that are due for
* deletion, along with their group IDs.
* <p/>
* Read-only.
*/
Map<GroupId, Collection<MessageId>> getMessagesToDelete(Transaction txn)
throws DbException;
/** /**
* Returns the metadata for all delivered messages in the given group. * Returns the metadata for all delivered messages in the given group.
* <p/> * <p/>
@@ -460,36 +395,6 @@ public interface DatabaseComponent extends TransactionManager {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact, together with their raw lengths. This may include
* messages that have already been sent and are not yet due for
* retransmission.
* <p/>
* Read-only.
*/
Map<MessageId, Integer> getUnackedMessagesToSend(Transaction txn,
ContactId c) throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages
* that have already been sent and are not yet due for retransmission.
* <p/>
* Read-only.
*/
long getUnackedMessageBytesToSend(Transaction txn, ContactId c)
throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be deleted, or {@link #NO_CLEANUP_DEADLINE}
* if no messages are scheduled to be deleted.
* <p/>
* Read-only.
*/
long getNextCleanupDeadline(Transaction txn) throws DbException;
/* /*
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may * message is due to be sent to the given contact. The returned value may
@@ -538,16 +443,6 @@ public interface DatabaseComponent extends TransactionManager {
Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t) Collection<TransportKeySet> getTransportKeys(Transaction txn, TransportId t)
throws DbException; throws DbException;
/**
* Returns the contact IDs and transport IDs for which the DB contains
* at least one set of transport keys. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Transaction txn) throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */
@@ -640,13 +535,6 @@ public interface DatabaseComponent extends TransactionManager {
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k) void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
*/
void setCleanupTimerDuration(Transaction txn, MessageId m, long duration)
throws DbException;
/** /**
* Marks the given contact as verified. * Marks the given contact as verified.
*/ */
@@ -669,12 +557,6 @@ public interface DatabaseComponent extends TransactionManager {
*/ */
void setMessagePermanent(Transaction txn, MessageId m) throws DbException; void setMessagePermanent(Transaction txn, MessageId m) throws DbException;
/**
* Marks the given message as not shared. This method is only meant for
* testing.
*/
void setMessageNotShared(Transaction txn, MessageId m) throws DbException;
/** /**
* Marks the given message as shared. * Marks the given message as shared.
*/ */
@@ -717,22 +599,6 @@ public interface DatabaseComponent extends TransactionManager {
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k) void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/**
* Starts the cleanup timer for the given message, if a timer duration
* has been set and the timer has not already been started.
*
* @return The cleanup deadline, or {@link #TIMER_NOT_STARTED} if no
* timer duration has been set for this message or its timer has already
* been started.
*/
long startCleanupTimer(Transaction txn, MessageId m) throws DbException;
/**
* Stops the cleanup timer for the given message, if the timer has been
* started.
*/
void stopCleanupTimer(Transaction txn, MessageId m) throws DbException;
/** /**
* Stores the given transport keys, deleting any keys they have replaced. * Stores the given transport keys, deleting any keys they have replaced.
*/ */

View File

@@ -57,7 +57,6 @@ public class Author implements Nameable {
/** /**
* Returns the author's name. * Returns the author's name.
*/ */
@Override
public String getName() { public String getName() {
return name; return name;
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.api.plugin.file; package org.briarproject.bramble.api.plugin;
public interface FileConstants { public interface FileConstants {

View File

@@ -22,11 +22,6 @@ public interface TransportConnectionWriter {
*/ */
int getMaxIdleTime(); int getMaxIdleTime();
/**
* Returns true if the transport is lossy and cheap.
*/
boolean isLossyAndCheap();
/** /**
* Returns an output stream for writing to the transport connection. * Returns an output stream for writing to the transport connection.
*/ */

View File

@@ -79,11 +79,6 @@ public abstract class AbstractDuplexTransportConnection
return plugin.getMaxIdleTime(); return plugin.getMaxIdleTime();
} }
@Override
public boolean isLossyAndCheap() {
return false;
}
@Override @Override
public OutputStream getOutputStream() throws IOException { public OutputStream getOutputStream() throws IOException {
return AbstractDuplexTransportConnection.this.getOutputStream(); return AbstractDuplexTransportConnection.this.getOutputStream();

View File

@@ -1,11 +0,0 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.plugin.TransportId;
public interface RemovableDriveConstants {
TransportId ID = new TransportId("org.briarproject.bramble.drive");
String PROP_PATH = "path";
String PROP_URI = "uri";
}

View File

@@ -1,46 +0,0 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import javax.annotation.Nullable;
@NotNullByDefault
public interface RemovableDriveManager {
/**
* Returns the currently running reader task, or null if no reader task
* is running.
*/
@Nullable
RemovableDriveTask getCurrentReaderTask();
/**
* Returns the currently running writer task, or null if no writer task
* is running.
*/
@Nullable
RemovableDriveTask getCurrentWriterTask();
/**
* Starts and returns a reader task, reading from a stream described by
* the given transport properties. If a reader task is already running,
* it will be returned and the argument will be ignored.
*/
RemovableDriveTask startReaderTask(TransportProperties p);
/**
* Starts and returns a writer task for the given contact, writing to
* a stream described by the given transport properties. If a writer task
* is already running, it will be returned and the arguments will be
* ignored.
*/
RemovableDriveTask startWriterTask(ContactId c, TransportProperties p);
/**
* Returns true if there is anything to send to the given contact.
*/
boolean isWriterTaskNeeded(ContactId c) throws DbException;
}

View File

@@ -1,65 +0,0 @@
package org.briarproject.bramble.api.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
public interface RemovableDriveTask extends Runnable {
/**
* Returns the {@link TransportProperties} that were used for creating
* this task.
*/
TransportProperties getTransportProperties();
/**
* Adds an observer to the task. The observer will be notified of state
* changes on the event thread. If the task has already finished, the
* observer will be notified of its final state.
*/
void addObserver(Consumer<State> observer);
/**
* Removes an observer from the task.
*/
void removeObserver(Consumer<State> observer);
class State {
private final long done, total;
private final boolean finished, success;
public State(long done, long total, boolean finished, boolean success) {
this.done = done;
this.total = total;
this.finished = finished;
this.success = success;
}
/**
* Returns the total length in bytes of the messages read or written
* so far, or zero if the total is unknown.
*/
public long getDone() {
return done;
}
/**
* Returns the total length in bytes of the messages that will have
* been read or written when the task is complete, or zero if the
* total is unknown.
*/
public long getTotal() {
return total;
}
public boolean isFinished() {
return finished;
}
public boolean isSuccess() {
return success;
}
}
}

View File

@@ -15,12 +15,6 @@ import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface SimplexPlugin extends Plugin { public interface SimplexPlugin extends Plugin {
/**
* Returns true if the transport is likely to lose streams and the cost of
* transmitting redundant copies of data is cheap.
*/
boolean isLossyAndCheap();
/** /**
* Attempts to create and return a reader for the given transport * Attempts to create and return a reader for the given transport
* properties. Returns null if a reader cannot be created. * properties. Returns null if a reader cannot be created.

View File

@@ -13,6 +13,4 @@ public interface DevConfig {
String getDevOnionAddress(); String getDevOnionAddress();
File getReportDir(); File getReportDir();
File getLogcatFile();
} }

View File

@@ -21,6 +21,5 @@ public interface ReportingConstants {
* Hidden service address for reporting crashes and feedback to the * Hidden service address for reporting crashes and feedback to the
* developers. * developers.
*/ */
String DEV_ONION_ADDRESS = String DEV_ONION_ADDRESS = "cwqmubyvnig3wag3.onion";
"b2nkt5doeamvdmjzfz7g42hk5vdtlnktlgzhel2bgjcc4v4jhnx2qrqd.onion";
} }

View File

@@ -16,7 +16,7 @@ public interface SyncSessionFactory {
PriorityHandler handler); PriorityHandler handler);
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, boolean eager, StreamWriter streamWriter); int maxLatency, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t, SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, StreamWriter streamWriter, int maxLatency, int maxIdleTime, StreamWriter streamWriter,

View File

@@ -18,13 +18,11 @@ public class MessagesSentEvent extends Event {
private final ContactId contactId; private final ContactId contactId;
private final Collection<MessageId> messageIds; private final Collection<MessageId> messageIds;
private final long totalLength;
public MessagesSentEvent(ContactId contactId, public MessagesSentEvent(ContactId contactId,
Collection<MessageId> messageIds, long totalLength) { Collection<MessageId> messageIds) {
this.contactId = contactId; this.contactId = contactId;
this.messageIds = messageIds; this.messageIds = messageIds;
this.totalLength = totalLength;
} }
public ContactId getContactId() { public ContactId getContactId() {
@@ -34,8 +32,4 @@ public class MessagesSentEvent extends Event {
public Collection<MessageId> getMessageIds() { public Collection<MessageId> getMessageIds() {
return messageIds; return messageIds;
} }
public long getTotalLength() {
return totalLength;
}
} }

View File

@@ -10,54 +10,23 @@ public interface IncomingMessageHook {
/** /**
* Called once for each incoming message that passes validation. * Called once for each incoming message that passes validation.
* <p>
* If an unexpected exception occurs while handling data that is assumed
* to be valid (e.g. locally created metadata), it may be sensible to
* rethrow the unexpected exception as a DbException so that delivery is
* attempted again at next startup. This will allow delivery to succeed if
* the unexpected exception was caused by a bug that has subsequently been
* fixed.
* *
* @param txn A read-write transaction * @param txn A read-write transaction
* @throws DbException if a database error occurs while delivering the * @return Whether or not this message should be shared
* message. Delivery will be attempted again at next startup. Throwing * @throws DbException Should only be used for real database errors.
* this exception has the same effect as returning * If this is thrown, delivery will be attempted again at next startup,
* {@link DeliveryAction#DEFER}. * whereas if an InvalidMessageException is thrown,
* @throws InvalidMessageException if the message is invalid in the context * the message will be permanently invalidated.
* of its dependencies. The message and any dependents will be marked as * @throws InvalidMessageException for any non-database error
* invalid and deleted along with their metadata. Throwing this exception * that occurs while handling remotely created data.
* has the same effect as returning {@link DeliveryAction#REJECT}. * This includes errors that occur while handling locally created data
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Throwing this will delete the incoming message and its metadata
* marking it as invalid in the database.
* Never rethrow DbException as InvalidMessageException!
*/ */
DeliveryAction incomingMessage(Transaction txn, Message m, Metadata meta) boolean incomingMessage(Transaction txn, Message m, Metadata meta)
throws DbException, InvalidMessageException; throws DbException, InvalidMessageException;
enum DeliveryAction {
/**
* The message and any dependent messages will be moved to the
* {@link MessageState#INVALID INVALID state} and deleted, along with
* their metadata.
*/
REJECT,
/**
* The message will be moved to the
* {@link MessageState#PENDING PENDING state}. Delivery will be
* attempted again at next startup.
*/
DEFER,
/**
* The message will be moved to the
* {@link MessageState#DELIVERED DELIVERED state} and shared.
*/
ACCEPT_SHARE,
/**
* The message will be moved to the
* {@link MessageState#DELIVERED DELIVERED state} and will not be
* shared.
*/
ACCEPT_DO_NOT_SHARE
}
} }

View File

@@ -1,33 +1,8 @@
package org.briarproject.bramble.api.sync.validation; package org.briarproject.bramble.api.sync.validation;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
public enum MessageState { public enum MessageState {
/** UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3);
* A remote message that has not yet been validated.
*/
UNKNOWN(0),
/**
* A remote message that has failed validation, has been
* {@link DeliveryAction#REJECT rejected} by the local sync client, or
* depends on another message that has failed validation or been rejected.
*/
INVALID(1),
/**
* A remote message that has passed validation and is awaiting delivery to
* the local sync client. The message will not be delivered until all its
* dependencies have been validated and delivered.
*/
PENDING(2),
/**
* A local message, or a remote message that has passed validation and
* been delivered to the local sync client.
*/
DELIVERED(3);
private final int value; private final int value;

View File

@@ -22,24 +22,8 @@ public interface KeyManager {
/** /**
* Derives and stores a set of rotation mode transport keys for * Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over the given transport and * communicating with the given contact over each transport and returns the
* returns the key set ID, or null if the transport is not supported. * key set IDs.
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
*
* @param alice True if the local party is Alice
* @param active Whether the derived keys can be used for outgoing streams
*/
@Nullable
KeySetId addRotationKeys(Transaction txn, ContactId c, TransportId t,
SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
/**
* Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over each supported transport and
* returns the key set IDs.
* <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.

View File

@@ -16,13 +16,8 @@ public interface StreamReaderFactory {
/** /**
* Creates an {@link InputStream InputStream} for reading from a contact * Creates an {@link InputStream InputStream} for reading from a contact
* exchange stream. * exchangestream.
*/ */
InputStream createContactExchangeStreamReader(InputStream in, InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates an {@link InputStream} for reading from a log stream.
*/
InputStream createLogStreamReader(InputStream in, SecretKey headerKey);
} }

View File

@@ -7,19 +7,17 @@ import java.io.OutputStream;
@NotNullByDefault @NotNullByDefault
public interface StreamWriterFactory { public interface StreamWriterFactory {
/** /**
* Creates a {@link StreamWriter} for writing to a transport stream. * Creates an {@link OutputStream OutputStream} for writing to a
* transport stream
*/ */
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx); StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
/** /**
* Creates a {@link StreamWriter} for writing to a contact exchange stream. * Creates an {@link OutputStream OutputStream} for writing to a contact
* exchange stream.
*/ */
StreamWriter createContactExchangeStreamWriter(OutputStream out, StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates a {@link StreamWriter} for writing to a log stream.
*/
StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey);
} }

View File

@@ -1,24 +0,0 @@
package org.briarproject.bramble.api.transport.agreement;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
@NotNullByDefault
public interface TransportKeyAgreementManager {
/**
* The unique ID of the transport key agreement client.
*/
ClientId CLIENT_ID =
new ClientId("org.briarproject.bramble.transport.agreement");
/**
* The current major version of the transport key agreement client.
*/
int MAJOR_VERSION = 0;
/**
* The current minor version of the transport key agreement client.
*/
int MINOR_VERSION = 0;
}

View File

@@ -6,9 +6,7 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault @NotNullByDefault
public class ValidationUtils { public class ValidationUtils {
@@ -66,9 +64,4 @@ public class ValidationUtils {
if (dictionary != null && dictionary.size() != size) if (dictionary != null && dictionary.size() != size)
throw new FormatException(); throw new FormatException();
} }
public static void checkRange(@Nullable Long l, long min, long max)
throws FormatException {
if (l != null && (l < min || l > max)) throw new FormatException();
}
} }

View File

@@ -1,8 +0,0 @@
package org.briarproject.bramble.test;
public interface TimeTravel {
void setCurrentTimeMillis(long now) throws InterruptedException;
void addCurrentTimeMillis(long add) throws InterruptedException;
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble; package org.briarproject.bramble;
import org.briarproject.bramble.cleanup.CleanupModule;
import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.contact.ContactModule;
import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.crypto.CryptoExecutorModule;
import org.briarproject.bramble.db.DatabaseExecutorModule; import org.briarproject.bramble.db.DatabaseExecutorModule;
@@ -11,13 +10,10 @@ import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
public interface BrambleCoreEagerSingletons { public interface BrambleCoreEagerSingletons {
void inject(CleanupModule.EagerSingletons init);
void inject(ContactModule.EagerSingletons init); void inject(ContactModule.EagerSingletons init);
void inject(CryptoExecutorModule.EagerSingletons init); void inject(CryptoExecutorModule.EagerSingletons init);
@@ -34,8 +30,6 @@ public interface BrambleCoreEagerSingletons {
void inject(RendezvousModule.EagerSingletons init); void inject(RendezvousModule.EagerSingletons init);
void inject(TransportKeyAgreementModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init); void inject(TransportModule.EagerSingletons init);
void inject(ValidationModule.EagerSingletons init); void inject(ValidationModule.EagerSingletons init);
@@ -45,7 +39,6 @@ public interface BrambleCoreEagerSingletons {
class Helper { class Helper {
public static void injectEagerSingletons(BrambleCoreEagerSingletons c) { public static void injectEagerSingletons(BrambleCoreEagerSingletons c) {
c.inject(new CleanupModule.EagerSingletons());
c.inject(new ContactModule.EagerSingletons()); c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoExecutorModule.EagerSingletons()); c.inject(new CryptoExecutorModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons()); c.inject(new DatabaseExecutorModule.EagerSingletons());
@@ -54,7 +47,6 @@ public interface BrambleCoreEagerSingletons {
c.inject(new RendezvousModule.EagerSingletons()); c.inject(new RendezvousModule.EagerSingletons());
c.inject(new PluginModule.EagerSingletons()); c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons());
c.inject(new TransportKeyAgreementModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons());
c.inject(new ValidationModule.EagerSingletons()); c.inject(new ValidationModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons()); c.inject(new VersioningModule.EagerSingletons());

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble; package org.briarproject.bramble;
import org.briarproject.bramble.cleanup.CleanupModule;
import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.client.ClientModule;
import org.briarproject.bramble.connection.ConnectionModule; import org.briarproject.bramble.connection.ConnectionModule;
import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.contact.ContactModule;
@@ -22,15 +21,15 @@ import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.settings.SettingsModule; import org.briarproject.bramble.settings.SettingsModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.transport.agreement.TransportKeyAgreementModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
import dagger.Module; import dagger.Module;
@Module(includes = { @Module(includes = {
CleanupModule.class,
ClientModule.class, ClientModule.class,
ClockModule.class,
ConnectionModule.class, ConnectionModule.class,
ContactModule.class, ContactModule.class,
CryptoModule.class, CryptoModule.class,
@@ -50,7 +49,6 @@ import dagger.Module;
RendezvousModule.class, RendezvousModule.class,
SettingsModule.class, SettingsModule.class,
SyncModule.class, SyncModule.class,
TransportKeyAgreementModule.class,
TransportModule.class, TransportModule.class,
ValidationModule.class, ValidationModule.class,
VersioningModule.class VersioningModule.class

View File

@@ -1,159 +0,0 @@
package org.briarproject.bramble.cleanup;
import org.briarproject.bramble.api.cleanup.CleanupHook;
import org.briarproject.bramble.api.cleanup.CleanupManager;
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class CleanupManagerImpl implements CleanupManager, Service, EventListener {
private static final Logger LOG =
getLogger(CleanupManagerImpl.class.getName());
private final Executor dbExecutor;
private final DatabaseComponent db;
private final TaskScheduler taskScheduler;
private final Clock clock;
private final Map<ClientMajorVersion, CleanupHook> hooks =
new ConcurrentHashMap<>();
private final Object lock = new Object();
@GuardedBy("lock")
private final Set<CleanupTask> pending = new HashSet<>();
@Inject
CleanupManagerImpl(@DatabaseExecutor Executor dbExecutor,
DatabaseComponent db, TaskScheduler taskScheduler, Clock clock) {
this.dbExecutor = dbExecutor;
this.db = db;
this.taskScheduler = taskScheduler;
this.clock = clock;
}
@Override
public void registerCleanupHook(ClientId c, int majorVersion,
CleanupHook hook) {
hooks.put(new ClientMajorVersion(c, majorVersion), hook);
}
@Override
public void startService() {
maybeScheduleTask(clock.currentTimeMillis());
}
@Override
public void stopService() {
}
@Override
public void eventOccurred(Event e) {
if (e instanceof CleanupTimerStartedEvent) {
CleanupTimerStartedEvent a = (CleanupTimerStartedEvent) e;
maybeScheduleTask(a.getCleanupDeadline());
}
}
private void maybeScheduleTask(long deadline) {
synchronized (lock) {
for (CleanupTask task : pending) {
if (task.deadline <= deadline) return;
}
CleanupTask task = new CleanupTask(deadline);
pending.add(task);
scheduleTask(task);
}
}
private void scheduleTask(CleanupTask task) {
long now = clock.currentTimeMillis();
long delay = max(0, task.deadline - now + BATCH_DELAY_MS);
if (LOG.isLoggable(INFO)) {
LOG.info("Scheduling cleanup task in " + delay + " ms");
}
taskScheduler.schedule(() -> deleteMessagesAndScheduleNextTask(task),
dbExecutor, delay, MILLISECONDS);
}
private void deleteMessagesAndScheduleNextTask(CleanupTask task) {
try {
synchronized (lock) {
pending.remove(task);
}
long deadline = db.transactionWithResult(false, txn -> {
deleteMessages(txn);
return db.getNextCleanupDeadline(txn);
});
if (deadline != NO_CLEANUP_DEADLINE) {
maybeScheduleTask(deadline);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private void deleteMessages(Transaction txn) throws DbException {
Map<GroupId, Collection<MessageId>> ids = db.getMessagesToDelete(txn);
for (Entry<GroupId, Collection<MessageId>> e : ids.entrySet()) {
GroupId groupId = e.getKey();
Collection<MessageId> messageIds = e.getValue();
if (LOG.isLoggable(INFO)) {
LOG.info(messageIds.size() + " messages to delete");
}
for (MessageId m : messageIds) db.stopCleanupTimer(txn, m);
Group group = db.getGroup(txn, groupId);
ClientMajorVersion cv = new ClientMajorVersion(group.getClientId(),
group.getMajorVersion());
CleanupHook hook = hooks.get(cv);
if (hook == null) {
throw new IllegalStateException("No cleanup hook for " + cv);
}
hook.deleteMessages(txn, groupId, messageIds);
}
}
private static class CleanupTask {
private final long deadline;
private CleanupTask(long deadline) {
this.deadline = deadline;
}
}
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.bramble.cleanup;
import org.briarproject.bramble.api.cleanup.CleanupManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class CleanupModule {
public static class EagerSingletons {
@Inject
CleanupManager cleanupManager;
}
@Provides
@Singleton
CleanupManager provideCleanupManager(LifecycleManager lifecycleManager,
EventBus eventBus, CleanupManagerImpl cleanupManager) {
lifecycleManager.registerService(cleanupManager);
eventBus.addListener(cleanupManager);
return cleanupManager;
}
}

View File

@@ -2,13 +2,11 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; 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.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -34,7 +32,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -42,7 +39,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -155,12 +151,6 @@ class ClientHelperImpl implements ClientHelper {
return metadataParser.parse(metadata); return metadataParser.parse(metadata);
} }
@Override
public Collection<MessageId> getMessageIds(Transaction txn, GroupId g,
BdfDictionary query) throws DbException, FormatException {
return db.getMessageIds(txn, g, metadataEncoder.encode(query));
}
@Override @Override
public BdfDictionary getMessageMetadataAsDictionary(MessageId m) public BdfDictionary getMessageMetadataAsDictionary(MessageId m)
throws DbException, FormatException { throws DbException, FormatException {
@@ -399,27 +389,4 @@ class ClientHelperImpl implements ClientHelper {
return tpMap; return tpMap;
} }
@Override
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {
try {
BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
}
@Override
public void setContactId(Transaction txn, GroupId contactGroupId,
ContactId c) throws DbException {
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, c.getInt()));
try {
mergeGroupMetadata(txn, contactGroupId, meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
} }

View File

@@ -71,10 +71,8 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx); w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId()); ContactId c = requireNonNull(ctx.getContactId());
// Use eager retransmission if the transport is lossy and cheap
return syncSessionFactory.createSimplexOutgoingSession(c, return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(), ctx.getTransportId(), w.getMaxLatency(), streamWriter);
streamWriter);
} }
} }

View File

@@ -9,16 +9,14 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
class ScryptKdf implements PasswordBasedKdf { class ScryptKdf implements PasswordBasedKdf {
private static final Logger LOG = private static final Logger LOG =
getLogger(ScryptKdf.class.getName()); Logger.getLogger(ScryptKdf.class.getName());
private static final int MIN_COST = 256; // Min parameter N private static final int MIN_COST = 256; // Min parameter N
private static final int MAX_COST = 1024 * 1024; // Max parameter N private static final int MAX_COST = 1024 * 1024; // Max parameter N
@@ -35,20 +33,10 @@ class ScryptKdf implements PasswordBasedKdf {
@Override @Override
public int chooseCostParameter() { public int chooseCostParameter() {
// Scrypt uses at least 128 * N * r bytes of memory. Don't use more
// than half of the JVM's max heap size or we may run out of memory.
// https://blog.filippo.io/the-scrypt-parameters/
long maxMemory = Runtime.getRuntime().maxMemory();
long maxCost = min(MAX_COST, maxMemory / BLOCK_SIZE / 256);
if (LOG.isLoggable(INFO) && maxCost < MAX_COST) {
LOG.info("Max cost capped at " + maxCost
+ " due to max heap size " + maxMemory);
}
// Increase the cost from min to max while measuring performance // Increase the cost from min to max while measuring performance
int cost = MIN_COST; int cost = MIN_COST;
while (cost * 2 <= maxCost && measureDuration(cost) * 2 <= TARGET_MS) { while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
cost *= 2; cost *= 2;
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("KDF cost parameter " + cost); LOG.info("KDF cost parameter " + cost);
return cost; return cost;

View File

@@ -36,10 +36,4 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
SecretKey headerKey) { SecretKey headerKey) {
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey); return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
} }
@Override
public StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey) {
return createContactExchangeStreamDecrypter(in, headerKey);
}
} }

View File

@@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
} }
@Override @Override
public StreamEncrypter createContactExchangeStreamEncrypter( public StreamEncrypter createContactExchangeStreamDecrypter(
OutputStream out, SecretKey headerKey) { OutputStream out, SecretKey headerKey) {
AuthenticatedCipher cipher = cipherProvider.get(); AuthenticatedCipher cipher = cipherProvider.get();
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH]; byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
@@ -60,10 +60,4 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce, return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
headerKey, frameKey); headerKey, frameKey);
} }
@Override
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey) {
return createContactExchangeStreamEncrypter(out, headerKey);
}
} }

View File

@@ -68,13 +68,6 @@ interface Database<T> {
*/ */
void close() throws DbException; void close() throws DbException;
/**
* Returns true if the dirty flag was set while opening the database,
* indicating that the database has not been shut down properly the last
* time it was closed and some data could be lost.
*/
boolean wasDirtyOnInitialisation();
/** /**
* Starts a new transaction and returns an object representing it. * Starts a new transaction and returns an object representing it.
*/ */
@@ -162,18 +155,6 @@ interface Database<T> {
KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k) KeySetId addTransportKeys(T txn, PendingContactId p, TransportKeys k)
throws DbException; throws DbException;
/**
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAnythingToSend(T txn, ContactId c, int maxLatency,
boolean eager) throws DbException;
/** /**
* Returns true if the database contains the given contact for the given * Returns true if the database contains the given contact for the given
* local pseudonym. * local pseudonym.
@@ -227,16 +208,6 @@ interface Database<T> {
*/ */
boolean containsTransport(T txn, TransportId t) throws DbException; boolean containsTransport(T txn, TransportId t) throws DbException;
/**
* Returns true if the database contains keys for communicating with the
* given contact over the given transport. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
boolean containsTransportKeys(T txn, ContactId c, TransportId t)
throws DbException;
/** /**
* Returns true if the database contains the given message, the message is * Returns true if the database contains the given message, the message is
* shared, and the visibility of the message's group to the given contact * shared, and the visibility of the message's group to the given contact
@@ -498,37 +469,11 @@ interface Database<T> {
* Returns the IDs of some messages that are eligible to be sent to the * Returns the IDs of some messages that are eligible to be sent to the
* given contact, up to the given total length. * given contact, up to the given total length.
* <p/> * <p/>
* Unlike {@link #getUnackedMessagesToSend(Object, ContactId)} this method
* does not return messages that have already been sent unless they are
* due for retransmission.
* <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength, Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength,
int maxLatency) throws DbException; int maxLatency) throws DbException;
/**
* Returns the IDs of all messages that are eligible to be sent to the
* given contact, together with their raw lengths.
* <p/>
* Unlike {@link #getMessagesToSend(Object, ContactId, int, int)} this
* method may return messages that have already been sent and are not yet
* due for retransmission.
* <p/>
* Read-only.
*/
Map<MessageId, Integer> getUnackedMessagesToSend(T txn, ContactId c)
throws DbException;
/**
* Returns the total length, including headers, of all messages that are
* eligible to be sent to the given contact. This may include messages
* that have already been sent and are not yet due for retransmission.
* <p/>
* Read-only.
*/
long getUnackedMessageBytesToSend(T txn, ContactId c) 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/>
@@ -552,25 +497,6 @@ interface Database<T> {
*/ */
Collection<MessageId> getMessagesToShare(T txn) throws DbException; Collection<MessageId> getMessagesToShare(T txn) throws DbException;
/**
* Returns the IDs of any messages of any messages that are due for
* deletion, along with their group IDs.
* <p/>
* Read-only.
*/
Map<GroupId, Collection<MessageId>> getMessagesToDelete(T txn)
throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be deleted, or
* {@link DatabaseComponent#NO_CLEANUP_DEADLINE} if no messages are
* scheduled to be deleted.
* <p/>
* Read-only.
*/
long getNextCleanupDeadline(T txn) throws DbException;
/** /**
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may * message is due to be sent to the given contact. The returned value may
@@ -628,16 +554,6 @@ interface Database<T> {
Collection<TransportKeySet> getTransportKeys(T txn, TransportId t) Collection<TransportKeySet> getTransportKeys(T txn, TransportId t)
throws DbException; throws DbException;
/**
* Returns the contact IDs and transport IDs for which the DB contains
* at least one set of transport keys. Handshake mode and rotation mode
* keys are included, whether activated or not.
* <p/>
* Read-only.
*/
Map<ContactId, Collection<TransportId>> getTransportsWithKeys(T txn)
throws DbException;
/** /**
* Increments the outgoing stream counter for the given transport keys. * Increments the outgoing stream counter for the given transport keys.
*/ */
@@ -690,10 +606,8 @@ interface Database<T> {
/** /**
* Marks a message as having been seen by the given contact. * Marks a message as having been seen by the given contact.
*
* @return True if the message was not already marked as seen.
*/ */
boolean raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException; void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
/** /**
* Removes a contact from the database. * Removes a contact from the database.
@@ -757,13 +671,6 @@ interface Database<T> {
*/ */
void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException; void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
/**
* Sets the cleanup timer duration for the given message. This does not
* start the message's cleanup timer.
*/
void setCleanupTimerDuration(T txn, MessageId m, long duration)
throws DbException;
/** /**
* Marks the given contact as verified. * Marks the given contact as verified.
*/ */
@@ -794,10 +701,9 @@ interface Database<T> {
void setMessagePermanent(T txn, MessageId m) throws DbException; void setMessagePermanent(T txn, MessageId m) throws DbException;
/** /**
* Marks the given message as shared or not. * Marks the given message as shared.
*/ */
void setMessageShared(T txn, MessageId m, boolean shared) void setMessageShared(T txn, MessageId m) throws DbException;
throws DbException;
/** /**
* Sets the validation and delivery state of the given message. * Sets the validation and delivery state of the given message.
@@ -824,22 +730,6 @@ interface Database<T> {
void setTransportKeysActive(T txn, TransportId t, KeySetId k) void setTransportKeysActive(T txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/**
* Starts the cleanup timer for the given message, if a timer duration
* has been set and the timer has not already been started.
*
* @return The cleanup deadline, or
* {@link DatabaseComponent#TIMER_NOT_STARTED} if no timer duration has
* been set for this message or its timer has already been started.
*/
long startCleanupTimer(T txn, MessageId m) throws DbException;
/**
* Stops the cleanup timer for the given message, if the timer has been
* started.
*/
void stopCleanupTimer(T txn, MessageId m) throws DbException;
/** /**
* Updates the transmission count, expiry time and estimated time of arrival * Updates the transmission count, expiry time and estimated time of arrival
* of the given message with respect to the given contact, using the latency * of the given message with respect to the given contact, using the latency

View File

@@ -1,12 +1,10 @@
package org.briarproject.bramble.db; package org.briarproject.bramble.db;
import org.briarproject.bramble.api.cleanup.event.CleanupTimerStartedEvent;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent; import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent; import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent; import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
@@ -341,15 +339,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.addTransportKeys(txn, p, k); return db.addTransportKeys(txn, p, k);
} }
@Override
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
int maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsAnythingToSend(txn, c, maxLatency, eager);
}
@Override @Override
public boolean containsContact(Transaction transaction, AuthorId remote, public boolean containsContact(Transaction transaction, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -380,13 +369,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.containsPendingContact(txn, p); return db.containsPendingContact(txn, p);
} }
@Override
public boolean containsTransportKeys(Transaction transaction, ContactId c,
TransportId t) throws DbException {
T txn = unbox(transaction);
return db.containsTransportKeys(txn, c, t);
}
@Override @Override
public void deleteMessage(Transaction transaction, MessageId m) public void deleteMessage(Transaction transaction, MessageId m)
throws DbException { throws DbException {
@@ -431,43 +413,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getMessagesToSend(txn, c, maxLength, maxLatency); db.getMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
Message message = db.getMessage(txn, m); messages.add(db.getMessage(txn, m));
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null; if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids, totalLength)); transaction.attach(new MessagesSentEvent(c, ids));
return messages;
}
@Override
public Collection<Message> generateBatch(Transaction transaction,
ContactId c, Collection<MessageId> ids, int maxLatency)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size());
List<MessageId> sentIds = new ArrayList<>(ids.size());
for (MessageId m : ids) {
if (db.containsVisibleMessage(txn, c, m)) {
Message message = db.getMessage(txn, m);
totalLength += message.getRawLength();
messages.add(message);
sentIds.add(m);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
}
}
if (messages.isEmpty()) return messages;
db.lowerRequestedFlag(txn, c, sentIds);
transaction.attach(new MessagesSentEvent(c, sentIds, totalLength));
return messages; return messages;
} }
@@ -512,17 +465,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = Collection<MessageId> ids =
db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency); db.getRequestedMessagesToSend(txn, c, maxLength, maxLatency);
long totalLength = 0;
List<Message> messages = new ArrayList<>(ids.size()); List<Message> messages = new ArrayList<>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
Message message = db.getMessage(txn, m); messages.add(db.getMessage(txn, m));
totalLength += message.getRawLength();
messages.add(message);
db.updateExpiryTimeAndEta(txn, c, m, maxLatency); db.updateExpiryTimeAndEta(txn, c, m, maxLatency);
} }
if (ids.isEmpty()) return null; if (ids.isEmpty()) return null;
db.lowerRequestedFlag(txn, c, ids); db.lowerRequestedFlag(txn, c, ids);
transaction.attach(new MessagesSentEvent(c, ids, totalLength)); transaction.attach(new MessagesSentEvent(c, ids));
return messages; return messages;
} }
@@ -626,15 +576,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageIds(txn, g); return db.getMessageIds(txn, g);
} }
@Override
public Collection<MessageId> getMessageIds(Transaction transaction,
GroupId g, Metadata query) throws DbException {
T txn = unbox(transaction);
if (!db.containsGroup(txn, g))
throw new NoSuchGroupException();
return db.getMessageIds(txn, g, query);
}
@Override @Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction) public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException { throws DbException {
@@ -656,13 +597,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessagesToShare(txn); return db.getMessagesToShare(txn);
} }
@Override
public Map<GroupId, Collection<MessageId>> getMessagesToDelete(
Transaction transaction) throws DbException {
T txn = unbox(transaction);
return db.getMessagesToDelete(txn);
}
@Override @Override
public Map<MessageId, Metadata> getMessageMetadata(Transaction transaction, public Map<MessageId, Metadata> getMessageMetadata(Transaction transaction,
GroupId g) throws DbException { GroupId g) throws DbException {
@@ -740,25 +674,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return status; return status;
} }
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(
Transaction transaction,
ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getUnackedMessagesToSend(txn, c);
}
@Override
public long getUnackedMessageBytesToSend(Transaction transaction,
ContactId c) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.getUnackedMessageBytesToSend(txn, c);
}
@Override @Override
public Map<MessageId, MessageState> getMessageDependencies( public Map<MessageId, MessageState> getMessageDependencies(
Transaction transaction, MessageId m) throws DbException { Transaction transaction, MessageId m) throws DbException {
@@ -777,13 +692,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageDependents(txn, m); return db.getMessageDependents(txn, m);
} }
@Override
public long getNextCleanupDeadline(Transaction transaction)
throws DbException {
T txn = unbox(transaction);
return db.getNextCleanupDeadline(txn);
}
@Override @Override
public long getNextSendTime(Transaction transaction, ContactId c) public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException { throws DbException {
@@ -832,13 +740,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getTransportKeys(txn, t); return db.getTransportKeys(txn, t);
} }
@Override
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Transaction transaction) throws DbException {
T txn = unbox(transaction);
return db.getTransportsWithKeys(txn);
}
@Override @Override
public void incrementStreamCounter(Transaction transaction, TransportId t, public void incrementStreamCounter(Transaction transaction, TransportId t,
KeySetId k) throws DbException { KeySetId k) throws DbException {
@@ -894,17 +795,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
Collection<MessageId> acked = new ArrayList<>(); Collection<MessageId> acked = new ArrayList<>();
for (MessageId m : a.getMessageIds()) { for (MessageId m : a.getMessageIds()) {
if (db.containsVisibleMessage(txn, c, m)) { if (db.containsVisibleMessage(txn, c, m)) {
if (db.raiseSeenFlag(txn, c, m)) { db.raiseSeenFlag(txn, c, m);
// This is the first time the message has been acked by acked.add(m);
// this contact. Start the cleanup timer (a no-op unless
// a cleanup deadline has been set for this message)
long deadline = db.startCleanupTimer(txn, m);
if (deadline != TIMER_NOT_STARTED) {
transaction.attach(new CleanupTimerStartedEvent(m,
deadline));
}
acked.add(m);
}
} }
} }
if (acked.size() > 0) { if (acked.size() > 0) {
@@ -1060,16 +952,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.removeTransportKeys(txn, t, k); db.removeTransportKeys(txn, t, k);
} }
@Override
public void setCleanupTimerDuration(Transaction transaction, MessageId m,
long duration) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.setCleanupTimerDuration(txn, m, duration);
}
@Override @Override
public void setContactVerified(Transaction transaction, ContactId c) public void setContactVerified(Transaction transaction, ContactId c)
throws DbException { throws DbException {
@@ -1088,7 +970,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
transaction.attach(new ContactAliasChangedEvent(c, alias));
db.setContactAlias(txn, c, alias); db.setContactAlias(txn, c, alias);
} }
@@ -1120,16 +1001,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setMessagePermanent(txn, m); db.setMessagePermanent(txn, m);
} }
@Override
public void setMessageNotShared(Transaction transaction, MessageId m)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.setMessageShared(txn, m, false);
}
@Override @Override
public void setMessageShared(Transaction transaction, MessageId m) public void setMessageShared(Transaction transaction, MessageId m)
throws DbException { throws DbException {
@@ -1139,7 +1010,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchMessageException(); throw new NoSuchMessageException();
if (db.getMessageState(txn, m) != DELIVERED) if (db.getMessageState(txn, m) != DELIVERED)
throw new IllegalArgumentException("Shared undelivered message"); throw new IllegalArgumentException("Shared undelivered message");
db.setMessageShared(txn, m, true); db.setMessageShared(txn, m);
transaction.attach(new MessageSharedEvent(m)); transaction.attach(new MessageSharedEvent(m));
} }
@@ -1211,30 +1082,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.setTransportKeysActive(txn, t, k); db.setTransportKeysActive(txn, t, k);
} }
@Override
public long startCleanupTimer(Transaction transaction, MessageId m)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
long deadline = db.startCleanupTimer(txn, m);
if (deadline != TIMER_NOT_STARTED) {
transaction.attach(new CleanupTimerStartedEvent(m, deadline));
}
return deadline;
}
@Override
public void stopCleanupTimer(Transaction transaction, MessageId m)
throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.stopCleanupTimer(txn, m);
}
@Override @Override
public void updateTransportKeys(Transaction transaction, public void updateTransportKeys(Transaction transaction,
Collection<TransportKeySet> keys) throws DbException { Collection<TransportKeySet> keys) throws DbException {

View File

@@ -37,10 +37,4 @@ interface DatabaseConstants {
* has passed since the last compaction. * has passed since the last compaction.
*/ */
long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30); long MAX_COMPACTION_INTERVAL_MS = DAYS.toMillis(30);
/**
* The {@link Settings} key under which the flag is stored indicating
* whether the database is marked as dirty.
*/
String DIRTY_KEY = "dirty";
} }

View File

@@ -84,14 +84,9 @@ class H2Database extends JdbcDatabase {
@Override @Override
public void close() throws DbException { public void close() throws DbException {
// H2 will close the database when the last connection closes // H2 will close the database when the last connection closes
Connection c = null;
try { try {
c = createConnection();
super.closeAllConnections(); super.closeAllConnections();
setDirty(c, false);
c.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(c, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }

View File

@@ -81,7 +81,6 @@ class HyperSqlDatabase extends JdbcDatabase {
try { try {
super.closeAllConnections(); super.closeAllConnections();
c = createConnection(); c = createConnection();
setDirty(c, false);
s = c.createStatement(); s = c.createStatement();
s.executeQuery("SHUTDOWN"); s.executeQuery("SHUTDOWN");
s.close(); s.close();

View File

@@ -51,7 +51,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -73,8 +72,6 @@ import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.db.DatabaseComponent.NO_CLEANUP_DEADLINE;
import static org.briarproject.bramble.api.db.DatabaseComponent.TIMER_NOT_STARTED;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@@ -84,7 +81,6 @@ import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERE
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.DIRTY_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY; import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS; import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY; import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
@@ -102,7 +98,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
static final int CODE_SCHEMA_VERSION = 48; static final int CODE_SCHEMA_VERSION = 47;
// Time period offsets for incoming transport keys // Time period offsets for incoming transport keys
private static final int OFFSET_PREV = -1; private static final int OFFSET_PREV = -1;
@@ -184,11 +180,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " state INT NOT NULL," + " state INT NOT NULL,"
+ " shared BOOLEAN NOT NULL," + " shared BOOLEAN NOT NULL,"
+ " temporary BOOLEAN NOT NULL," + " temporary BOOLEAN NOT NULL,"
// Null if no timer duration has been set
+ " cleanupTimerDuration BIGINT,"
// Null if no timer duration has been set or the timer
// hasn't started
+ " cleanupDeadline BIGINT,"
+ " length INT NOT NULL," + " length INT NOT NULL,"
+ " raw BLOB," // Null if message has been deleted + " raw BLOB," // Null if message has been deleted
+ " PRIMARY KEY (messageId)," + " PRIMARY KEY (messageId),"
@@ -345,15 +336,6 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp" "CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
+ " ON statuses (contactId, timestamp)"; + " ON statuses (contactId, timestamp)";
private static final String
INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP =
"CREATE INDEX IF NOT EXISTS statusesByContactIdTxCountTimestamp"
+ " ON statuses (contactId, txCount, timestamp)";
private static final String INDEX_MESSAGES_BY_CLEANUP_DEADLINE =
"CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)";
private static final Logger LOG = private static final Logger LOG =
getLogger(JdbcDatabase.class.getName()); getLogger(JdbcDatabase.class.getName());
@@ -372,14 +354,9 @@ abstract class JdbcDatabase implements Database<Connection> {
@GuardedBy("connectionsLock") @GuardedBy("connectionsLock")
private boolean closed = false; private boolean closed = false;
private volatile boolean wasDirtyOnInitialisation = false;
protected abstract Connection createConnection() protected abstract Connection createConnection()
throws DbException, SQLException; throws DbException, SQLException;
// Used exclusively during open to compact the database after schema
// migrations or after DatabaseConstants#MAX_COMPACTION_INTERVAL_MS has
// elapsed
protected abstract void compactAndClose() throws DbException; protected abstract void compactAndClose() throws DbException;
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory, JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
@@ -404,19 +381,13 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
if (reopen) { if (reopen) {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE); Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
wasDirtyOnInitialisation = isDirty(s);
compact = migrateSchema(txn, s, listener) || isCompactionDue(s); compact = migrateSchema(txn, s, listener) || isCompactionDue(s);
} else { } else {
wasDirtyOnInitialisation = false;
createTables(txn); createTables(txn);
initialiseSettings(txn); initialiseSettings(txn);
compact = false; compact = false;
} }
if (LOG.isLoggable(INFO)) {
LOG.info("db dirty? " + wasDirtyOnInitialisation);
}
createIndexes(txn); createIndexes(txn);
setDirty(txn, true);
commitTransaction(txn); commitTransaction(txn);
} catch (DbException e) { } catch (DbException e) {
abortTransaction(txn); abortTransaction(txn);
@@ -443,11 +414,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean wasDirtyOnInitialisation() {
return wasDirtyOnInitialisation;
}
/** /**
* Compares the schema version stored in the database with the schema * Compares the schema version stored in the database with the schema
* version used by the current code and applies any suitable migrations to * version used by the current code and applies any suitable migrations to
@@ -497,8 +463,7 @@ abstract class JdbcDatabase implements Database<Connection> {
new Migration43_44(dbTypes), new Migration43_44(dbTypes),
new Migration44_45(), new Migration44_45(),
new Migration45_46(), new Migration45_46(),
new Migration46_47(dbTypes), new Migration46_47(dbTypes)
new Migration47_48()
); );
} }
@@ -523,16 +488,6 @@ abstract class JdbcDatabase implements Database<Connection> {
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE); mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
} }
private boolean isDirty(Settings s) {
return s.getBoolean(DIRTY_KEY, false);
}
protected void setDirty(Connection txn, boolean dirty) throws DbException {
Settings s = new Settings();
s.putBoolean(DIRTY_KEY, dirty);
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
}
private void initialiseSettings(Connection txn) throws DbException { private void initialiseSettings(Connection txn) throws DbException {
Settings s = new Settings(); Settings s = new Settings();
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION); s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION);
@@ -576,8 +531,6 @@ abstract class JdbcDatabase implements Database<Connection> {
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);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TX_COUNT_TIMESTAMP);
s.executeUpdate(INDEX_MESSAGES_BY_CLEANUP_DEADLINE);
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s, LOG, WARNING); tryToClose(s, LOG, WARNING);
@@ -1127,55 +1080,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsAnythingToSend(Connection txn, ContactId c,
int maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND ack = TRUE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
rs = ps.executeQuery();
boolean acksToSend = rs.next();
rs.close();
ps.close();
if (acksToSend) return true;
if (eager) {
sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
long eta = now + maxLatency;
sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR eta > ?)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, eta);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsContact(Connection txn, AuthorId remote, public boolean containsContact(Connection txn, AuthorId remote,
AuthorId local) throws DbException { AuthorId local) throws DbException {
@@ -1333,29 +1237,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public boolean containsTransportKeys(Connection txn, ContactId c,
TransportId t) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT NULL FROM outgoingKeys"
+ " WHERE contactId = ? AND transportId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setString(2, t.getString());
rs = ps.executeQuery();
boolean found = rs.next();
rs.close();
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public boolean containsVisibleMessage(Connection txn, ContactId c, public boolean containsVisibleMessage(Connection txn, ContactId c,
MessageId m) throws DbException { MessageId m) throws DbException {
@@ -1409,9 +1290,7 @@ abstract class JdbcDatabase implements Database<Connection> {
public void deleteMessage(Connection txn, MessageId m) throws DbException { public void deleteMessage(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "UPDATE messages" String sql = "UPDATE messages SET raw = NULL WHERE messageId = ?";
+ " SET raw = NULL, cleanupDeadline = NULL"
+ " WHERE messageId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
@@ -1890,6 +1769,7 @@ abstract class JdbcDatabase implements Database<Connection> {
// Return early if there are no matches // Return early if there are no matches
if (intersection.isEmpty()) return Collections.emptySet(); if (intersection.isEmpty()) return Collections.emptySet();
} }
if (intersection == null) throw new AssertionError();
return intersection; return intersection;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs, LOG, WARNING); tryToClose(rs, LOG, WARNING);
@@ -2284,63 +2164,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Map<MessageId, Integer> getUnackedMessagesToSend(Connection txn,
ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT length, messageId FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " ORDER BY txCount, timestamp";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
Map<MessageId, Integer> results = new LinkedHashMap<>();
while (rs.next()) {
int length = rs.getInt(1);
MessageId id = new MessageId(rs.getBytes(2));
results.put(id, length);
}
rs.close();
ps.close();
return results;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public long getUnackedMessageBytesToSend(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT SUM(length) FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery();
rs.next();
long total = rs.getInt(1);
rs.close();
ps.close();
return total;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public Collection<MessageId> getMessagesToValidate(Connection txn) public Collection<MessageId> getMessagesToValidate(Connection txn)
throws DbException { throws DbException {
@@ -2403,39 +2226,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Map<GroupId, Collection<MessageId>> getMessagesToDelete(
Connection txn) throws DbException {
long now = clock.currentTimeMillis();
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId, groupId FROM messages"
+ " WHERE cleanupDeadline <= ?";
ps = txn.prepareStatement(sql);
ps.setLong(1, now);
rs = ps.executeQuery();
Map<GroupId, Collection<MessageId>> ids = new HashMap<>();
while (rs.next()) {
MessageId m = new MessageId(rs.getBytes(1));
GroupId g = new GroupId(rs.getBytes(2));
Collection<MessageId> messageIds = ids.get(g);
if (messageIds == null) {
messageIds = new ArrayList<>();
ids.put(g, messageIds);
}
messageIds.add(m);
}
rs.close();
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public long getNextSendTime(Connection txn, ContactId c) public long getNextSendTime(Connection txn, ContactId c)
throws DbException { throws DbException {
@@ -2466,31 +2256,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public long getNextCleanupDeadline(Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
String sql = "SELECT cleanupDeadline FROM messages"
+ " WHERE cleanupDeadline IS NOT NULL"
+ " ORDER BY cleanupDeadline LIMIT 1";
s = txn.createStatement();
rs = s.executeQuery(sql);
long nextDeadline = NO_CLEANUP_DEADLINE;
if (rs.next()) {
nextDeadline = rs.getLong(1);
if (rs.next()) throw new AssertionError();
}
rs.close();
s.close();
return nextDeadline;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public PendingContact getPendingContact(Connection txn, PendingContactId p) public PendingContact getPendingContact(Connection txn, PendingContactId p)
throws DbException { throws DbException {
@@ -2710,38 +2475,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public Map<ContactId, Collection<TransportId>> getTransportsWithKeys(
Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
String sql = "SELECT DISTINCT contactId, transportId"
+ " FROM outgoingKeys";
s = txn.createStatement();
rs = s.executeQuery(sql);
Map<ContactId, Collection<TransportId>> ids = new HashMap<>();
while (rs.next()) {
ContactId c = new ContactId(rs.getInt(1));
TransportId t = new TransportId(rs.getString(2));
Collection<TransportId> transportIds = ids.get(c);
if (transportIds == null) {
transportIds = new ArrayList<>();
ids.put(c, transportIds);
}
transportIds.add(t);
}
rs.close();
s.close();
return ids;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(s, LOG, WARNING);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void incrementStreamCounter(Connection txn, TransportId t, public void incrementStreamCounter(Connection txn, TransportId t,
KeySetId k) throws DbException { KeySetId k) throws DbException {
@@ -3043,7 +2776,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public boolean raiseSeenFlag(Connection txn, ContactId c, MessageId m) public void raiseSeenFlag(Connection txn, ContactId c, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
@@ -3055,7 +2788,6 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
return affected == 1;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps, LOG, WARNING); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
@@ -3289,25 +3021,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public void setCleanupTimerDuration(Connection txn, MessageId m,
long duration) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages SET cleanupTimerDuration = ?"
+ " WHERE messageId = ? AND cleanupTimerDuration IS NULL";
ps = txn.prepareStatement(sql);
ps.setLong(1, duration);
ps.setBytes(2, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void setContactVerified(Connection txn, ContactId c) public void setContactVerified(Connection txn, ContactId c)
throws DbException { throws DbException {
@@ -3415,24 +3128,22 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void setMessageShared(Connection txn, MessageId m, boolean shared) public void setMessageShared(Connection txn, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "UPDATE messages SET shared = ?" String sql = "UPDATE messages SET shared = TRUE"
+ " WHERE messageId = ?"; + " WHERE messageId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBoolean(1, shared); ps.setBytes(1, m.getBytes());
ps.setBytes(2, m.getBytes());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
// Update denormalised column in statuses // Update denormalised column in statuses
sql = "UPDATE statuses SET messageShared = ?" sql = "UPDATE statuses SET messageShared = TRUE"
+ " WHERE messageId = ?"; + " WHERE messageId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBoolean(1, shared); ps.setBytes(1, m.getBytes());
ps.setBytes(2, m.getBytes());
affected = ps.executeUpdate(); affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
@@ -3561,60 +3272,6 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override
public long startCleanupTimer(Connection txn, MessageId m)
throws DbException {
long now = clock.currentTimeMillis();
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "UPDATE messages"
+ " SET cleanupDeadline = ? + cleanupTimerDuration"
+ " WHERE messageId = ?"
+ " AND cleanupTimerDuration IS NOT NULL"
+ " AND cleanupDeadline IS NULL";
ps = txn.prepareStatement(sql);
ps.setLong(1, now);
ps.setBytes(2, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
if (affected == 0) return TIMER_NOT_STARTED;
sql = "SELECT cleanupDeadline FROM messages WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
long deadline = rs.getLong(1);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return deadline;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void stopCleanupTimer(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages SET cleanupDeadline = NULL"
+ " WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override @Override
public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m, public void updateExpiryTimeAndEta(Connection txn, ContactId c, MessageId m,
int maxLatency) throws DbException { int maxLatency) throws DbException {

View File

@@ -1,47 +0,0 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration47_48 implements Migration<Connection> {
private static final Logger LOG = getLogger(Migration47_48.class.getName());
@Override
public int getStartVersion() {
return 47;
}
@Override
public int getEndVersion() {
return 48;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
// Null if no timer duration has been set
s.execute("ALTER TABLE messages"
+ " ADD COLUMN cleanupTimerDuration BIGINT");
// Null if no timer duration has been set or the timer
// hasn't started
s.execute("ALTER TABLE messages"
+ " ADD COLUMN cleanupDeadline BIGINT");
s.execute("CREATE INDEX IF NOT EXISTS messagesByCleanupDeadline"
+ " ON messages (cleanupDeadline)");
} catch (SQLException e) {
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
}

View File

@@ -34,12 +34,12 @@ class KeyAgreementTransport {
Logger.getLogger(KeyAgreementTransport.class.getName()); Logger.getLogger(KeyAgreementTransport.class.getName());
// Accept records with current protocol version, known record type // Accept records with current protocol version, known record type
private static final Predicate<Record> ACCEPT = r -> private static Predicate<Record> ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType()); isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type // Ignore records with current protocol version, unknown record type
private static final Predicate<Record> IGNORE = r -> private static Predicate<Record> IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION && r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType()); !isKnownRecordType(r.getRecordType());

View File

@@ -1,623 +0,0 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Multiset;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
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.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.event.RemoteTransportPropertiesUpdatedEvent;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_ADDRESS_IS_REFLECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_EVER_CONNECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_ADDRESS_IS_REFLECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_EVER_CONNECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.macToBytes;
import static org.briarproject.bramble.util.StringUtils.macToString;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class AbstractBluetoothPlugin<S, SS> implements BluetoothPlugin,
EventListener {
private static final Logger LOG =
getLogger(AbstractBluetoothPlugin.class.getName());
private final BluetoothConnectionLimiter connectionLimiter;
final BluetoothConnectionFactory<S> connectionFactory;
private final Executor ioExecutor, wakefulIoExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final PluginCallback callback;
private final int maxLatency, maxIdleTime;
private final AtomicBoolean used = new AtomicBoolean(false);
private final AtomicBoolean everConnected = new AtomicBoolean(false);
protected final PluginState state = new PluginState();
protected final Semaphore discoverSemaphore = new Semaphore(1);
private volatile String contactConnectionsUuid = null;
abstract void initialiseAdapter() throws IOException;
abstract boolean isAdapterEnabled();
/**
* Returns the local Bluetooth address, or null if no valid address can
* be found.
*/
@Nullable
abstract String getBluetoothAddress();
abstract SS openServerSocket(String uuid) throws IOException;
abstract void tryToClose(@Nullable SS ss);
abstract DuplexTransportConnection acceptConnection(SS ss)
throws IOException;
abstract boolean isValidAddress(String address);
abstract DuplexTransportConnection connectTo(String address, String uuid)
throws IOException;
@Nullable
abstract DuplexTransportConnection discoverAndConnect(String uuid);
AbstractBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
BluetoothConnectionFactory<S> connectionFactory,
Executor ioExecutor,
Executor wakefulIoExecutor,
SecureRandom secureRandom,
Backoff backoff,
PluginCallback callback,
int maxLatency,
int maxIdleTime) {
this.connectionLimiter = connectionLimiter;
this.connectionFactory = connectionFactory;
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
}
void onAdapterEnabled() {
LOG.info("Bluetooth enabled");
// We may not have been able to get the local address before
ioExecutor.execute(this::updateProperties);
if (getState() == INACTIVE) bind();
}
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
connectionLimiter.allConnectionsClosed();
// The server socket may not have been closed automatically
SS ss = state.clearServerSocket();
if (ss != null) {
LOG.info("Closing server socket");
tryToClose(ss);
}
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
return maxIdleTime;
}
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
Settings settings = callback.getSettings();
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
DEFAULT_PREF_PLUGIN_ENABLE);
everConnected.set(settings.getBoolean(PREF_EVER_CONNECTED,
DEFAULT_PREF_EVER_CONNECTED));
state.setStarted(enabledByUser);
try {
initialiseAdapter();
} catch (IOException e) {
throw new PluginException(e);
}
updateProperties();
if (enabledByUser && isAdapterEnabled()) bind();
}
private void bind() {
ioExecutor.execute(() -> {
if (getState() != INACTIVE) return;
// Bind a server socket to accept connections from contacts
SS ss;
try {
ss = openServerSocket(contactConnectionsUuid);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
}
if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss);
return;
}
backoff.reset();
acceptContactConnections(ss);
});
}
private void updateProperties() {
TransportProperties p = callback.getLocalProperties();
String address = p.get(PROP_ADDRESS);
String uuid = p.get(PROP_UUID);
Settings s = callback.getSettings();
boolean isReflected = s.getBoolean(PREF_ADDRESS_IS_REFLECTED,
DEFAULT_PREF_ADDRESS_IS_REFLECTED);
boolean changed = false;
if (address == null || isReflected) {
address = getBluetoothAddress();
if (LOG.isLoggable(INFO)) {
LOG.info("Local address " + scrubMacAddress(address));
}
if (address == null) {
if (everConnected.get()) {
address = getReflectedAddress();
if (LOG.isLoggable(INFO)) {
LOG.info("Reflected address " +
scrubMacAddress(address));
}
if (address != null) {
changed = true;
isReflected = true;
}
}
} else {
changed = true;
isReflected = false;
}
}
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
changed = true;
}
contactConnectionsUuid = uuid;
if (changed) {
p = new TransportProperties();
// If we previously used a reflected address and there's no longer
// a reflected address with enough votes to be used, we'll continue
// to use the old reflected address until there's a new winner
if (address != null) p.put(PROP_ADDRESS, address);
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
s = new Settings();
s.putBoolean(PREF_ADDRESS_IS_REFLECTED, isReflected);
callback.mergeSettings(s);
}
}
@Nullable
private String getReflectedAddress() {
// Count the number of votes for each reflected address
String key = REFLECTED_PROPERTY_PREFIX + PROP_ADDRESS;
Multiset<String> votes = new Multiset<>();
for (TransportProperties p : callback.getRemoteProperties()) {
String address = p.get(key);
if (address != null && isValidAddress(address)) votes.add(address);
}
// If an address gets more than half of the votes, accept it
int total = votes.getTotal();
for (String address : votes.keySet()) {
if (votes.getCount(address) * 2 > total) return address;
}
return null;
}
private void acceptContactConnections(SS ss) {
while (true) {
DuplexTransportConnection conn;
try {
conn = acceptConnection(ss);
} catch (IOException e) {
// This is expected when the server socket is closed
LOG.info("Server socket closed");
state.clearServerSocket();
return;
}
LOG.info("Connection received");
connectionLimiter.connectionOpened(conn);
backoff.reset();
setEverConnected();
callback.handleConnection(conn);
}
}
private void setEverConnected() {
if (!everConnected.getAndSet(true)) {
ioExecutor.execute(() -> {
Settings s = new Settings();
s.putBoolean(PREF_EVER_CONNECTED, true);
callback.mergeSettings(s);
// Contacts may already have sent a reflected address
updateProperties();
});
}
}
@Override
public void stop() {
SS ss = state.setStopped();
tryToClose(ss);
}
@Override
public State getState() {
return state.getState();
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
}
}
private void connect(TransportProperties p, ConnectionHandler h) {
String address = p.get(PROP_ADDRESS);
if (isNullOrEmpty(address)) return;
String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return;
wakefulIoExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p);
if (d != null) {
backoff.reset();
setEverConnected();
h.handleConnection(d);
}
});
}
@Nullable
private DuplexTransportConnection connect(String address, String uuid) {
// Validate the address
if (!isValidAddress(address)) {
if (LOG.isLoggable(WARNING))
// Not scrubbing here to be able to figure out the problem
LOG.warning("Invalid address " + address);
return null;
}
// Validate the UUID
try {
//noinspection ResultOfMethodCallIgnored
UUID.fromString(uuid);
} catch (IllegalArgumentException e) {
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
return null;
}
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
try {
DuplexTransportConnection conn = connectTo(address, uuid);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubMacAddress(address));
return conn;
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Could not connect to " + scrubMacAddress(address));
return null;
}
}
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (getState() != ACTIVE) return null;
if (!connectionLimiter.canOpenContactConnection()) return null;
String address = p.get(PROP_ADDRESS);
if (isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return null;
DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) connectionLimiter.connectionOpened(conn);
return conn;
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (getState() != ACTIVE) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
// Bind a server socket for receiving key agreement connections
SS ss;
try {
ss = openServerSocket(uuid);
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
if (getState() != ACTIVE) {
tryToClose(ss);
return null;
}
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH);
String address = getBluetoothAddress();
if (address != null) descriptor.add(macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
if (getState() != ACTIVE) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
DuplexTransportConnection conn;
if (descriptor.size() == 1) {
if (LOG.isLoggable(INFO)) {
LOG.info("Discovering address for key agreement UUID " +
uuid);
}
conn = discoverAndConnect(uuid);
} else {
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
conn = connect(address, uuid);
}
if (conn != null) {
connectionLimiter.connectionOpened(conn);
setEverConnected();
}
return conn;
}
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return macToString(mac);
}
@Override
public boolean isDiscovering() {
return discoverSemaphore.availablePermits() == 0;
}
@Override
public DuplexTransportConnection discoverAndConnectForSetup(String uuid) {
DuplexTransportConnection conn = discoverAndConnect(uuid);
if (conn != null) {
connectionLimiter.connectionOpened(conn);
setEverConnected();
}
return conn;
}
@Override
public boolean supportsRendezvous() {
return false;
}
@Override
public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
boolean alice, ConnectionHandler incoming) {
throw new UnsupportedOperationException();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
} else if (e instanceof KeyAgreementListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
RemoteTransportPropertiesUpdatedEvent r =
(RemoteTransportPropertiesUpdatedEvent) e;
if (r.getTransportId().equals(ID)) {
ioExecutor.execute(this::updateProperties);
}
}
}
@IoExecutor
private void onSettingsUpdated(Settings settings) {
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
DEFAULT_PREF_PLUGIN_ENABLE);
SS ss = state.setEnabledByUser(enabledByUser);
State s = getState();
if (ss != null) {
LOG.info("Disabled by user, closing server socket");
tryToClose(ss);
} else if (s == INACTIVE) {
if (isAdapterEnabled()) {
LOG.info("Enabled by user, opening server socket");
bind();
} else {
LOG.info("Enabled by user but adapter is disabled");
}
}
}
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
private final SS ss;
private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
super(descriptor);
this.ss = ss;
}
@Override
public KeyAgreementConnection accept() throws IOException {
DuplexTransportConnection conn = acceptConnection(ss);
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
connectionLimiter.connectionOpened(conn);
return new KeyAgreementConnection(conn, ID);
}
@Override
public void close() {
tryToClose(ss);
}
}
@ThreadSafe
@NotNullByDefault
private class PluginState {
@GuardedBy("this")
private boolean started = false,
stopped = false,
enabledByUser = false;
@GuardedBy("this")
@Nullable
private SS serverSocket = null;
private synchronized void setStarted(boolean enabledByUser) {
started = true;
this.enabledByUser = enabledByUser;
callback.pluginStateChanged(getState());
}
@Nullable
private synchronized SS setStopped() {
stopped = true;
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
@Nullable
private synchronized SS setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser;
SS ss = null;
if (!enabledByUser) {
ss = serverSocket;
serverSocket = null;
}
callback.pluginStateChanged(getState());
return ss;
}
private synchronized boolean setServerSocket(SS ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
callback.pluginStateChanged(getState());
return true;
}
@Nullable
private synchronized SS clearServerSocket() {
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
private synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
private synchronized int getReasonsDisabled() {
return getState() == DISABLED ? REASON_USER : 0;
}
}
}

View File

@@ -1,18 +1,606 @@
package org.briarproject.bramble.plugin.bluetooth; package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Multiset;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
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.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
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.properties.TransportProperties;
import org.briarproject.bramble.api.properties.event.RemoteTransportPropertiesUpdatedEvent;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
@NotNullByDefault import static java.util.logging.Level.INFO;
public interface BluetoothPlugin extends DuplexPlugin { import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_ADDRESS_IS_REFLECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_EVER_CONNECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_ADDRESS_IS_REFLECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_EVER_CONNECTED;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.macToBytes;
import static org.briarproject.bramble.util.StringUtils.macToString;
boolean isDiscovering(); @MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
private static final Logger LOG =
getLogger(BluetoothPlugin.class.getName());
final BluetoothConnectionLimiter connectionLimiter;
final BluetoothConnectionFactory<S> connectionFactory;
private final Executor ioExecutor, wakefulIoExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final PluginCallback callback;
private final int maxLatency, maxIdleTime;
private final AtomicBoolean used = new AtomicBoolean(false);
private final AtomicBoolean everConnected = new AtomicBoolean(false);
protected final PluginState state = new PluginState();
private volatile String contactConnectionsUuid = null;
abstract void initialiseAdapter() throws IOException;
abstract boolean isAdapterEnabled();
/**
* Returns the local Bluetooth address, or null if no valid address can
* be found.
*/
@Nullable
abstract String getBluetoothAddress();
abstract SS openServerSocket(String uuid) throws IOException;
abstract void tryToClose(@Nullable SS ss);
abstract DuplexTransportConnection acceptConnection(SS ss)
throws IOException;
abstract boolean isValidAddress(String address);
abstract DuplexTransportConnection connectTo(String address, String uuid)
throws IOException;
@Nullable @Nullable
DuplexTransportConnection discoverAndConnectForSetup(String uuid); abstract DuplexTransportConnection discoverAndConnect(String uuid);
void stopDiscoverAndConnect(); BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
BluetoothConnectionFactory<S> connectionFactory,
Executor ioExecutor,
Executor wakefulIoExecutor,
SecureRandom secureRandom,
Backoff backoff,
PluginCallback callback,
int maxLatency,
int maxIdleTime) {
this.connectionLimiter = connectionLimiter;
this.connectionFactory = connectionFactory;
this.ioExecutor = ioExecutor;
this.wakefulIoExecutor = wakefulIoExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
}
void onAdapterEnabled() {
LOG.info("Bluetooth enabled");
// We may not have been able to get the local address before
ioExecutor.execute(this::updateProperties);
if (getState() == INACTIVE) bind();
}
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
connectionLimiter.allConnectionsClosed();
// The server socket may not have been closed automatically
SS ss = state.clearServerSocket();
if (ss != null) {
LOG.info("Closing server socket");
tryToClose(ss);
}
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
return maxIdleTime;
}
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
Settings settings = callback.getSettings();
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
DEFAULT_PREF_PLUGIN_ENABLE);
everConnected.set(settings.getBoolean(PREF_EVER_CONNECTED,
DEFAULT_PREF_EVER_CONNECTED));
state.setStarted(enabledByUser);
try {
initialiseAdapter();
} catch (IOException e) {
throw new PluginException(e);
}
updateProperties();
if (enabledByUser && isAdapterEnabled()) bind();
}
private void bind() {
ioExecutor.execute(() -> {
if (getState() != INACTIVE) return;
// Bind a server socket to accept connections from contacts
SS ss;
try {
ss = openServerSocket(contactConnectionsUuid);
} catch (IOException e) {
logException(LOG, WARNING, e);
return;
}
if (!state.setServerSocket(ss)) {
LOG.info("Closing redundant server socket");
tryToClose(ss);
return;
}
backoff.reset();
acceptContactConnections(ss);
});
}
private void updateProperties() {
TransportProperties p = callback.getLocalProperties();
String address = p.get(PROP_ADDRESS);
String uuid = p.get(PROP_UUID);
Settings s = callback.getSettings();
boolean isReflected = s.getBoolean(PREF_ADDRESS_IS_REFLECTED,
DEFAULT_PREF_ADDRESS_IS_REFLECTED);
boolean changed = false;
if (address == null || isReflected) {
address = getBluetoothAddress();
if (LOG.isLoggable(INFO)) {
LOG.info("Local address " + scrubMacAddress(address));
}
if (address == null) {
if (everConnected.get()) {
address = getReflectedAddress();
if (LOG.isLoggable(INFO)) {
LOG.info("Reflected address " +
scrubMacAddress(address));
}
if (address != null) {
changed = true;
isReflected = true;
}
}
} else {
changed = true;
isReflected = false;
}
}
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
changed = true;
}
contactConnectionsUuid = uuid;
if (changed) {
p = new TransportProperties();
// If we previously used a reflected address and there's no longer
// a reflected address with enough votes to be used, we'll continue
// to use the old reflected address until there's a new winner
if (address != null) p.put(PROP_ADDRESS, address);
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
s = new Settings();
s.putBoolean(PREF_ADDRESS_IS_REFLECTED, isReflected);
callback.mergeSettings(s);
}
}
@Nullable
private String getReflectedAddress() {
// Count the number of votes for each reflected address
String key = REFLECTED_PROPERTY_PREFIX + PROP_ADDRESS;
Multiset<String> votes = new Multiset<>();
for (TransportProperties p : callback.getRemoteProperties()) {
String address = p.get(key);
if (address != null && isValidAddress(address)) votes.add(address);
}
// If an address gets more than half of the votes, accept it
int total = votes.getTotal();
for (String address : votes.keySet()) {
if (votes.getCount(address) * 2 > total) return address;
}
return null;
}
private void acceptContactConnections(SS ss) {
while (true) {
DuplexTransportConnection conn;
try {
conn = acceptConnection(ss);
} catch (IOException e) {
// This is expected when the server socket is closed
LOG.info("Server socket closed");
state.clearServerSocket();
return;
}
LOG.info("Connection received");
connectionLimiter.connectionOpened(conn);
backoff.reset();
setEverConnected();
callback.handleConnection(conn);
}
}
private void setEverConnected() {
if (!everConnected.getAndSet(true)) {
ioExecutor.execute(() -> {
Settings s = new Settings();
s.putBoolean(PREF_EVER_CONNECTED, true);
callback.mergeSettings(s);
// Contacts may already have sent a reflected address
updateProperties();
});
}
}
@Override
public void stop() {
SS ss = state.setStopped();
tryToClose(ss);
}
@Override
public State getState() {
return state.getState();
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
connect(p.getFirst(), p.getSecond());
}
}
private void connect(TransportProperties p, ConnectionHandler h) {
String address = p.get(PROP_ADDRESS);
if (isNullOrEmpty(address)) return;
String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return;
wakefulIoExecutor.execute(() -> {
DuplexTransportConnection d = createConnection(p);
if (d != null) {
backoff.reset();
setEverConnected();
h.handleConnection(d);
}
});
}
@Nullable
private DuplexTransportConnection connect(String address, String uuid) {
// Validate the address
if (!isValidAddress(address)) {
if (LOG.isLoggable(WARNING))
// Not scrubbing here to be able to figure out the problem
LOG.warning("Invalid address " + address);
return null;
}
// Validate the UUID
try {
//noinspection ResultOfMethodCallIgnored
UUID.fromString(uuid);
} catch (IllegalArgumentException e) {
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
return null;
}
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
try {
DuplexTransportConnection conn = connectTo(address, uuid);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubMacAddress(address));
return conn;
} catch (IOException e) {
if (LOG.isLoggable(INFO))
LOG.info("Could not connect to " + scrubMacAddress(address));
return null;
}
}
@Override
public DuplexTransportConnection createConnection(TransportProperties p) {
if (getState() != ACTIVE) return null;
if (!connectionLimiter.canOpenContactConnection()) return null;
String address = p.get(PROP_ADDRESS);
if (isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return null;
DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) connectionLimiter.connectionOpened(conn);
return conn;
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (getState() != ACTIVE) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
// Bind a server socket for receiving key agreement connections
SS ss;
try {
ss = openServerSocket(uuid);
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
if (getState() != ACTIVE) {
tryToClose(ss);
return null;
}
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH);
String address = getBluetoothAddress();
if (address != null) descriptor.add(macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
if (getState() != ACTIVE) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
DuplexTransportConnection conn;
if (descriptor.size() == 1) {
if (LOG.isLoggable(INFO)) {
LOG.info("Discovering address for key agreement UUID " +
uuid);
}
conn = discoverAndConnect(uuid);
} else {
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
conn = connect(address, uuid);
}
if (conn != null) {
connectionLimiter.connectionOpened(conn);
setEverConnected();
}
return conn;
}
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return macToString(mac);
}
@Override
public boolean supportsRendezvous() {
return false;
}
@Override
public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
boolean alice, ConnectionHandler incoming) {
throw new UnsupportedOperationException();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
} else if (e instanceof KeyAgreementListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
} else if (e instanceof RemoteTransportPropertiesUpdatedEvent) {
RemoteTransportPropertiesUpdatedEvent r =
(RemoteTransportPropertiesUpdatedEvent) e;
if (r.getTransportId().equals(ID)) {
ioExecutor.execute(this::updateProperties);
}
}
}
@IoExecutor
private void onSettingsUpdated(Settings settings) {
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE,
DEFAULT_PREF_PLUGIN_ENABLE);
SS ss = state.setEnabledByUser(enabledByUser);
State s = getState();
if (ss != null) {
LOG.info("Disabled by user, closing server socket");
tryToClose(ss);
} else if (s == INACTIVE) {
if (isAdapterEnabled()) {
LOG.info("Enabled by user, opening server socket");
bind();
} else {
LOG.info("Enabled by user but adapter is disabled");
}
}
}
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
private final SS ss;
private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
super(descriptor);
this.ss = ss;
}
@Override
public KeyAgreementConnection accept() throws IOException {
DuplexTransportConnection conn = acceptConnection(ss);
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
connectionLimiter.connectionOpened(conn);
return new KeyAgreementConnection(conn, ID);
}
@Override
public void close() {
tryToClose(ss);
}
}
@ThreadSafe
@NotNullByDefault
protected class PluginState {
@GuardedBy("this")
private boolean started = false,
stopped = false,
enabledByUser = false;
@GuardedBy("this")
@Nullable
private SS serverSocket = null;
synchronized void setStarted(boolean enabledByUser) {
started = true;
this.enabledByUser = enabledByUser;
callback.pluginStateChanged(getState());
}
@Nullable
synchronized SS setStopped() {
stopped = true;
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
@Nullable
synchronized SS setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser;
SS ss = null;
if (!enabledByUser) {
ss = serverSocket;
serverSocket = null;
}
callback.pluginStateChanged(getState());
return ss;
}
synchronized boolean setServerSocket(SS ss) {
if (stopped || serverSocket != null) return false;
serverSocket = ss;
callback.pluginStateChanged(getState());
return true;
}
@Nullable
synchronized SS clearServerSocket() {
SS ss = serverSocket;
serverSocket = null;
callback.pluginStateChanged(getState());
return ss;
}
synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? REASON_USER : 0;
}
}
} }

View File

@@ -1,119 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@NotNullByDefault
abstract class AbstractRemovableDrivePlugin implements SimplexPlugin {
private static final Logger LOG =
getLogger(AbstractRemovableDrivePlugin.class.getName());
private final int maxLatency;
abstract InputStream openInputStream(TransportProperties p)
throws IOException;
abstract OutputStream openOutputStream(TransportProperties p)
throws IOException;
AbstractRemovableDrivePlugin(int maxLatency) {
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Unused for simplex transports
throw new UnsupportedOperationException();
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
public State getState() {
return ACTIVE;
}
@Override
public int getReasonsDisabled() {
return 0;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(
Collection<Pair<TransportProperties, ConnectionHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public boolean isLossyAndCheap() {
return true;
}
@Override
public TransportConnectionReader createReader(TransportProperties p) {
try {
return new TransportInputStreamReader(openInputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
@Override
public TransportConnectionWriter createWriter(TransportProperties p) {
try {
return new TransportOutputStreamWriter(this, openOutputStream(p));
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
}

View File

@@ -15,8 +15,8 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.FileConstants.PROP_PATH;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;

View File

@@ -36,11 +36,6 @@ class FileTransportWriter implements TransportConnectionWriter {
return plugin.getMaxIdleTime(); return plugin.getMaxIdleTime();
} }
@Override
public boolean isLossyAndCheap() {
return plugin.isLossyAndCheap();
}
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return out; return out;

View File

@@ -1,102 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory.MAX_LATENCY;
@ThreadSafe
@NotNullByDefault
class RemovableDriveManagerImpl
implements RemovableDriveManager, RemovableDriveTaskRegistry {
private final Executor ioExecutor;
private final DatabaseComponent db;
private final RemovableDriveTaskFactory taskFactory;
private final Object lock = new Object();
@GuardedBy("lock")
private RemovableDriveTask reader = null;
@GuardedBy("lock")
private RemovableDriveTask writer = null;
@Inject
RemovableDriveManagerImpl(@IoExecutor Executor ioExecutor,
DatabaseComponent db, RemovableDriveTaskFactory taskFactory) {
this.ioExecutor = ioExecutor;
this.db = db;
this.taskFactory = taskFactory;
}
@Nullable
@Override
public RemovableDriveTask getCurrentReaderTask() {
synchronized (lock) {
return reader;
}
}
@Nullable
@Override
public RemovableDriveTask getCurrentWriterTask() {
synchronized (lock) {
return writer;
}
}
@Override
public RemovableDriveTask startReaderTask(TransportProperties p) {
RemovableDriveTask created;
synchronized (lock) {
if (reader != null) return reader;
reader = created = taskFactory.createReader(this, p);
}
ioExecutor.execute(created);
return created;
}
@Override
public RemovableDriveTask startWriterTask(ContactId c,
TransportProperties p) {
RemovableDriveTask created;
synchronized (lock) {
if (writer != null) return writer;
writer = created = taskFactory.createWriter(this, c, p);
}
ioExecutor.execute(created);
return created;
}
@Override
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
return db.transactionWithResult(true, txn ->
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
}
@Override
public void removeReader(RemovableDriveTask task) {
synchronized (lock) {
if (reader == task) reader = null;
}
}
@Override
public void removeWriter(RemovableDriveTask task) {
synchronized (lock) {
if (writer == task) writer = null;
}
}
}

View File

@@ -1,25 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.plugin.file.RemovableDriveManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class RemovableDriveModule {
@Provides
@Singleton
RemovableDriveManager provideRemovableDriveManager(
RemovableDriveManagerImpl removableDriveManager) {
return removableDriveManager;
}
@Provides
RemovableDriveTaskFactory provideTaskFactory(
RemovableDriveTaskFactoryImpl taskFactory) {
return taskFactory;
}
}

View File

@@ -1,38 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.PROP_PATH;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class RemovableDrivePlugin extends AbstractRemovableDrivePlugin {
RemovableDrivePlugin(int maxLatency) {
super(maxLatency);
}
@Override
InputStream openInputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileInputStream(path);
}
@Override
OutputStream openOutputStream(TransportProperties p) throws IOException {
String path = p.get(PROP_PATH);
if (isNullOrEmpty(path)) throw new IllegalArgumentException();
return new FileOutputStream(path);
}
}

View File

@@ -1,41 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@Immutable
@NotNullByDefault
public class RemovableDrivePluginFactory implements SimplexPluginFactory {
static final int MAX_LATENCY = (int) DAYS.toMillis(14);
@Inject
RemovableDrivePluginFactory() {
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return MAX_LATENCY;
}
@Nullable
@Override
public SimplexPlugin createPlugin(PluginCallback callback) {
return new RemovableDrivePlugin(MAX_LATENCY);
}
}

View File

@@ -1,69 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@NotNullByDefault
class RemovableDriveReaderTask extends RemovableDriveTaskImpl {
private final static Logger LOG =
getLogger(RemovableDriveReaderTask.class.getName());
RemovableDriveReaderTask(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, transportProperties);
}
@Override
public void run() {
TransportConnectionReader r =
getPlugin().createReader(transportProperties);
if (r == null) {
LOG.warning("Failed to create reader");
registry.removeReader(this);
setSuccess(false);
return;
}
connectionManager.manageIncomingConnection(ID, new DecoratedReader(r));
}
private class DecoratedReader implements TransportConnectionReader {
private final TransportConnectionReader delegate;
private DecoratedReader(TransportConnectionReader delegate) {
this.delegate = delegate;
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public void dispose(boolean exception, boolean recognised)
throws IOException {
delegate.dispose(exception, recognised);
registry.removeReader(RemovableDriveReaderTask.this);
setSuccess(!exception && recognised);
}
}
}

View File

@@ -1,16 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
@NotNullByDefault
interface RemovableDriveTaskFactory {
RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
TransportProperties p);
RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p);
}

View File

@@ -1,55 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class RemovableDriveTaskFactoryImpl implements RemovableDriveTaskFactory {
private final DatabaseComponent db;
private final Executor eventExecutor;
private final PluginManager pluginManager;
private final ConnectionManager connectionManager;
private final EventBus eventBus;
@Inject
RemovableDriveTaskFactoryImpl(
DatabaseComponent db,
@EventExecutor Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus) {
this.db = db;
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
}
@Override
public RemovableDriveTask createReader(RemovableDriveTaskRegistry registry,
TransportProperties p) {
return new RemovableDriveReaderTask(eventExecutor, pluginManager,
connectionManager, eventBus, registry, p);
}
@Override
public RemovableDriveTask createWriter(RemovableDriveTaskRegistry registry,
ContactId c, TransportProperties p) {
return new RemovableDriveWriterTask(db, eventExecutor, pluginManager,
connectionManager, eventBus, registry, c, p);
}
}

View File

@@ -1,116 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.Math.min;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
@ThreadSafe
@NotNullByDefault
abstract class RemovableDriveTaskImpl implements RemovableDriveTask {
private final Executor eventExecutor;
private final PluginManager pluginManager;
final ConnectionManager connectionManager;
final EventBus eventBus;
final RemovableDriveTaskRegistry registry;
final TransportProperties transportProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<Consumer<State>> observers = new ArrayList<>();
@GuardedBy("lock")
private State state = new State(0, 0, false, false);
RemovableDriveTaskImpl(
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
TransportProperties transportProperties) {
this.eventExecutor = eventExecutor;
this.pluginManager = pluginManager;
this.connectionManager = connectionManager;
this.eventBus = eventBus;
this.registry = registry;
this.transportProperties = transportProperties;
}
@Override
public TransportProperties getTransportProperties() {
return transportProperties;
}
@Override
public void addObserver(Consumer<State> o) {
State state;
synchronized (lock) {
observers.add(o);
state = this.state;
}
if (state.isFinished()) {
eventExecutor.execute(() -> o.accept(state));
}
}
@Override
public void removeObserver(Consumer<State> o) {
synchronized (lock) {
observers.remove(o);
}
}
SimplexPlugin getPlugin() {
return (SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
}
void setTotal(long total) {
synchronized (lock) {
state = new State(state.getDone(), total, state.isFinished(),
state.isSuccess());
notifyObservers();
}
}
void addDone(long done) {
synchronized (lock) {
// Done and total come from different sources; make them consistent
done = min(state.getDone() + done, state.getTotal());
state = new State(done, state.getTotal(), state.isFinished(),
state.isSuccess());
notifyObservers();
}
}
void setSuccess(boolean success) {
synchronized (lock) {
state = new State(state.getDone(), state.getTotal(), true, success);
notifyObservers();
}
}
@GuardedBy("lock")
private void notifyObservers() {
List<Consumer<State>> observers = new ArrayList<>(this.observers);
State state = this.state;
eventExecutor.execute(() -> {
for (Consumer<State> o : observers) o.accept(state);
});
}
}

View File

@@ -1,12 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.file.RemovableDriveTask;
@NotNullByDefault
interface RemovableDriveTaskRegistry {
void removeReader(RemovableDriveTask task);
void removeWriter(RemovableDriveTask task);
}

View File

@@ -1,126 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.ID;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class RemovableDriveWriterTask extends RemovableDriveTaskImpl
implements EventListener {
private static final Logger LOG =
getLogger(RemovableDriveWriterTask.class.getName());
private final DatabaseComponent db;
private final ContactId contactId;
RemovableDriveWriterTask(
DatabaseComponent db,
Executor eventExecutor,
PluginManager pluginManager,
ConnectionManager connectionManager,
EventBus eventBus,
RemovableDriveTaskRegistry registry,
ContactId contactId,
TransportProperties transportProperties) {
super(eventExecutor, pluginManager, connectionManager, eventBus,
registry, transportProperties);
this.db = db;
this.contactId = contactId;
}
@Override
public void run() {
SimplexPlugin plugin = getPlugin();
TransportConnectionWriter w = plugin.createWriter(transportProperties);
if (w == null) {
LOG.warning("Failed to create writer");
registry.removeWriter(this);
setSuccess(false);
return;
}
try {
setTotal(db.transactionWithResult(true, txn ->
db.getUnackedMessageBytesToSend(txn, contactId)));
} catch (DbException e) {
logException(LOG, WARNING, e);
registry.removeWriter(this);
setSuccess(false);
return;
}
eventBus.addListener(this);
connectionManager.manageOutgoingConnection(contactId, ID,
new DecoratedWriter(w));
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessagesSentEvent) {
MessagesSentEvent m = (MessagesSentEvent) e;
if (contactId.equals(m.getContactId())) {
if (LOG.isLoggable(INFO)) {
LOG.info(m.getMessageIds().size() + " messages sent");
}
addDone(m.getTotalLength());
}
}
}
private class DecoratedWriter implements TransportConnectionWriter {
private final TransportConnectionWriter delegate;
private DecoratedWriter(TransportConnectionWriter delegate) {
this.delegate = delegate;
}
@Override
public int getMaxLatency() {
return delegate.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return delegate.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return delegate.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public void dispose(boolean exception) throws IOException {
delegate.dispose(exception);
registry.removeWriter(RemovableDriveWriterTask.this);
eventBus.removeListener(RemovableDriveWriterTask.this);
setSuccess(!exception);
}
}
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import java.io.InputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportInputStreamReader implements TransportConnectionReader {
private static final Logger LOG =
getLogger(TransportInputStreamReader.class.getName());
private final InputStream in;
TransportInputStreamReader(InputStream in) {
this.in = in;
}
@Override
public InputStream getInputStream() {
return in;
}
@Override
public void dispose(boolean exception, boolean recognised) {
tryToClose(in, LOG, WARNING);
}
}

View File

@@ -1,52 +0,0 @@
package org.briarproject.bramble.plugin.file;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class TransportOutputStreamWriter implements TransportConnectionWriter {
private static final Logger LOG =
getLogger(TransportOutputStreamWriter.class.getName());
private final SimplexPlugin plugin;
private final OutputStream out;
TransportOutputStreamWriter(SimplexPlugin plugin, OutputStream out) {
this.plugin = plugin;
this.out = out;
}
@Override
public int getMaxLatency() {
return plugin.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return plugin.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return plugin.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public void dispose(boolean exception) {
tryToClose(out, LOG, WARNING);
}
}

View File

@@ -44,7 +44,6 @@ import static org.briarproject.bramble.api.properties.TransportPropertyConstants
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX; import static org.briarproject.bramble.api.properties.TransportPropertyConstants.REFLECTED_PROPERTY_PREFIX;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable @Immutable
@@ -116,8 +115,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
@Override @Override
public DeliveryAction incomingMessage(Transaction txn, Message m, public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
Metadata meta) throws DbException, InvalidMessageException { throws DbException, InvalidMessageException {
try { try {
// Find the latest update for this transport, if any // Find the latest update for this transport, if any
BdfDictionary d = metadataParser.parse(meta); BdfDictionary d = metadataParser.parse(meta);
@@ -132,14 +131,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
// We've already received a newer update - delete this one // We've already received a newer update - delete this one
db.deleteMessage(txn, m.getId()); db.deleteMessage(txn, m.getId());
db.deleteMessageMetadata(txn, m.getId()); db.deleteMessageMetadata(txn, m.getId());
return ACCEPT_DO_NOT_SHARE; return false;
} }
} }
txn.attach(new RemoteTransportPropertiesUpdatedEvent(t)); txn.attach(new RemoteTransportPropertiesUpdatedEvent(t));
} catch (FormatException e) { } catch (FormatException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
return ACCEPT_DO_NOT_SHARE; return false;
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -15,7 +16,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
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.MessageId;
import org.briarproject.bramble.api.sync.SyncRecordWriter; 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.Versions; import org.briarproject.bramble.api.sync.Versions;
@@ -23,11 +23,7 @@ import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.transport.StreamWriter; import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@@ -66,7 +62,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final int maxLatency; private final int maxLatency;
private final boolean eager;
private final StreamWriter streamWriter; private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter; private final SyncRecordWriter recordWriter;
private final AtomicInteger outstandingQueries; private final AtomicInteger outstandingQueries;
@@ -76,7 +71,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, TransportId transportId, EventBus eventBus, ContactId contactId, TransportId transportId,
int maxLatency, boolean eager, StreamWriter streamWriter, int maxLatency, StreamWriter streamWriter,
SyncRecordWriter recordWriter) { SyncRecordWriter recordWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
@@ -84,7 +79,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.eager = eager;
this.streamWriter = streamWriter; this.streamWriter = streamWriter;
this.recordWriter = recordWriter; this.recordWriter = recordWriter;
outstandingQueries = new AtomicInteger(2); // One per type of record outstandingQueries = new AtomicInteger(2); // One per type of record
@@ -99,9 +93,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
// Send our supported protocol versions // Send our supported protocol versions
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS)); recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
// Start a query for each type of record // Start a query for each type of record
dbExecutor.execute(this::generateAck); dbExecutor.execute(new GenerateAck());
if (eager) dbExecutor.execute(this::loadUnackedMessageIds); dbExecutor.execute(new GenerateBatch());
else dbExecutor.execute(this::generateBatch);
// Write records until interrupted or no more records to write // Write records until interrupted or no more records to write
try { try {
while (!interrupted) { while (!interrupted) {
@@ -146,110 +139,81 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
} }
} }
@DatabaseExecutor private class GenerateAck implements Runnable {
private void loadUnackedMessageIds() {
if (interrupted) return; @DatabaseExecutor
try { @Override
Map<MessageId, Integer> ids = db.transactionWithResult(true, txn -> public void run() {
db.getUnackedMessagesToSend(txn, contactId)); if (interrupted) return;
if (LOG.isLoggable(INFO)) { try {
LOG.info(ids.size() + " unacked messages to send"); Ack a = db.transactionWithNullableResult(false, txn ->
db.generateAck(txn, contactId, MAX_MESSAGE_IDS));
if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null));
if (a == null) decrementOutstandingQueries();
else writerTasks.add(new WriteAck(a));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
if (ids.isEmpty()) decrementOutstandingQueries();
else dbExecutor.execute(() -> generateEagerBatch(ids));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
} }
@DatabaseExecutor private class WriteAck implements ThrowingRunnable<IOException> {
private void generateEagerBatch(Map<MessageId, Integer> ids) {
if (interrupted) return; private final Ack ack;
// Take some message IDs from `ids` to form a batch
Collection<MessageId> batchIds = new ArrayList<>(); private WriteAck(Ack ack) {
long totalLength = 0; this.ack = ack;
Iterator<Entry<MessageId, Integer>> it = ids.entrySet().iterator();
while (it.hasNext()) {
// Check whether the next message will fit in the batch
Entry<MessageId, Integer> e = it.next();
int length = e.getValue();
if (totalLength + length > MAX_RECORD_PAYLOAD_BYTES) break;
// Add the message to the batch
it.remove();
batchIds.add(e.getKey());
totalLength += length;
} }
if (batchIds.isEmpty()) throw new AssertionError();
try { @IoExecutor
Collection<Message> batch = @Override
db.transactionWithResult(false, txn -> public void run() throws IOException {
db.generateBatch(txn, contactId, batchIds, if (interrupted) return;
maxLatency)); recordWriter.writeAck(ack);
writerTasks.add(() -> writeEagerBatch(batch, ids)); LOG.info("Sent ack");
} catch (DbException e) { dbExecutor.execute(new GenerateAck());
logException(LOG, WARNING, e);
interrupt();
} }
} }
@IoExecutor private class GenerateBatch implements Runnable {
private void writeEagerBatch(Collection<Message> batch,
Map<MessageId, Integer> ids) throws IOException {
if (interrupted) return;
for (Message m : batch) recordWriter.writeMessage(m);
LOG.info("Sent eager batch");
if (ids.isEmpty()) decrementOutstandingQueries();
else dbExecutor.execute(() -> generateEagerBatch(ids));
}
@DatabaseExecutor @DatabaseExecutor
private void generateAck() { @Override
if (interrupted) return; public void run() {
try { if (interrupted) return;
Ack a = db.transactionWithNullableResult(false, txn -> try {
db.generateAck(txn, contactId, MAX_MESSAGE_IDS)); Collection<Message> b =
if (LOG.isLoggable(INFO)) db.transactionWithNullableResult(false, txn ->
LOG.info("Generated ack: " + (a != null)); db.generateBatch(txn, contactId,
if (a == null) decrementOutstandingQueries(); MAX_RECORD_PAYLOAD_BYTES, maxLatency));
else writerTasks.add(() -> writeAck(a)); if (LOG.isLoggable(INFO))
} catch (DbException e) { LOG.info("Generated batch: " + (b != null));
logException(LOG, WARNING, e); if (b == null) decrementOutstandingQueries();
interrupt(); else writerTasks.add(new WriteBatch(b));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
}
} }
} }
@IoExecutor private class WriteBatch implements ThrowingRunnable<IOException> {
private void writeAck(Ack ack) throws IOException {
if (interrupted) return;
recordWriter.writeAck(ack);
LOG.info("Sent ack");
dbExecutor.execute(this::generateAck);
}
@DatabaseExecutor private final Collection<Message> batch;
private void generateBatch() {
if (interrupted) return; private WriteBatch(Collection<Message> batch) {
try { this.batch = batch;
Collection<Message> b =
db.transactionWithNullableResult(false, txn ->
db.generateBatch(txn, contactId,
MAX_RECORD_PAYLOAD_BYTES, maxLatency));
if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null));
if (b == null) decrementOutstandingQueries();
else writerTasks.add(() -> writeBatch(b));
} catch (DbException e) {
logException(LOG, WARNING, e);
interrupt();
} }
}
@IoExecutor @IoExecutor
private void writeBatch(Collection<Message> batch) throws IOException { @Override
if (interrupted) return; public void run() throws IOException {
for (Message m : batch) recordWriter.writeMessage(m); if (interrupted) return;
LOG.info("Sent batch"); for (Message m : batch) recordWriter.writeMessage(m);
dbExecutor.execute(this::generateBatch); LOG.info("Sent batch");
dbExecutor.execute(new GenerateBatch());
}
} }
} }

View File

@@ -60,12 +60,12 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
@Override @Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t, public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, boolean eager, StreamWriter streamWriter) { int maxLatency, StreamWriter streamWriter) {
OutputStream out = streamWriter.getOutputStream(); OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter = SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out); recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t, return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
maxLatency, eager, streamWriter, recordWriter); maxLatency, streamWriter, recordWriter);
} }
@Override @Override

View File

@@ -1,6 +0,0 @@
package org.briarproject.bramble.sync;
interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

@@ -20,7 +20,6 @@ import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction;
import org.briarproject.bramble.api.sync.validation.MessageState; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.sync.validation.MessageValidator; import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.api.sync.validation.ValidationManager; import org.briarproject.bramble.api.sync.validation.ValidationManager;
@@ -41,10 +40,6 @@ import javax.inject.Inject;
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.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_DO_NOT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.ACCEPT_SHARE;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.DEFER;
import static org.briarproject.bramble.api.sync.validation.IncomingMessageHook.DeliveryAction.REJECT;
import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
@@ -190,19 +185,16 @@ class ValidationManagerImpl implements ValidationManager, Service,
int majorVersion = g.getMajorVersion(); int majorVersion = g.getMajorVersion();
Metadata meta = Metadata meta =
db.getMessageMetadataForValidator(txn, id); db.getMessageMetadataForValidator(txn, id);
DeliveryAction action = DeliveryResult result =
deliverMessage(txn, m, c, majorVersion, meta); deliverMessage(txn, m, c, majorVersion, meta);
if (action == REJECT) { if (result.valid) {
invalidateMessage(txn, id); addPendingDependents(txn, id, pending);
if (result.share) {
db.setMessageShared(txn, id);
toShare.addAll(states.keySet());
}
} else {
addDependentsToInvalidate(txn, id, invalidate); addDependentsToInvalidate(txn, id, invalidate);
} else if (action == ACCEPT_SHARE) {
db.setMessageState(txn, m.getId(), DELIVERED);
addPendingDependents(txn, id, pending);
db.setMessageShared(txn, id);
toShare.addAll(states.keySet());
} else if (action == ACCEPT_DO_NOT_SHARE) {
db.setMessageState(txn, m.getId(), DELIVERED);
addPendingDependents(txn, id, pending);
} }
} }
} }
@@ -283,21 +275,16 @@ 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) {
DeliveryAction action = DeliveryResult result =
deliverMessage(txn, m, c, majorVersion, meta); deliverMessage(txn, m, c, majorVersion, meta);
if (action == REJECT) { if (result.valid) {
invalidateMessage(txn, id); addPendingDependents(txn, id, pending);
if (result.share) {
db.setMessageShared(txn, id);
toShare.addAll(dependencies);
}
} else {
addDependentsToInvalidate(txn, id, invalidate); addDependentsToInvalidate(txn, id, invalidate);
} else if (action == DEFER) {
db.setMessageState(txn, id, PENDING);
} else if (action == ACCEPT_SHARE) {
db.setMessageState(txn, id, DELIVERED);
addPendingDependents(txn, id, pending);
db.setMessageShared(txn, id);
toShare.addAll(dependencies);
} else if (action == ACCEPT_DO_NOT_SHARE) {
db.setMessageState(txn, id, DELIVERED);
addPendingDependents(txn, id, pending);
} }
} else { } else {
db.setMessageState(txn, id, PENDING); db.setMessageState(txn, id, PENDING);
@@ -317,21 +304,23 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
@DatabaseExecutor @DatabaseExecutor
private DeliveryAction deliverMessage(Transaction txn, Message m, private DeliveryResult deliverMessage(Transaction txn, Message m,
ClientId c, int majorVersion, Metadata meta) { ClientId c, int majorVersion, Metadata meta) throws DbException {
// Deliver the message to the client if it has registered a hook // Deliver the message to the client if it's registered a hook
boolean shareMsg = false;
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion); ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
IncomingMessageHook hook = hooks.get(cv); IncomingMessageHook hook = hooks.get(cv);
if (hook == null) return ACCEPT_DO_NOT_SHARE; if (hook != null) {
try { try {
return hook.incomingMessage(txn, m, meta); shareMsg = hook.incomingMessage(txn, m, meta);
} catch (DbException e) { } catch (InvalidMessageException e) {
logException(LOG, INFO, e); logException(LOG, INFO, e);
return DEFER; invalidateMessage(txn, m.getId());
} catch (InvalidMessageException e) { return new DeliveryResult(false, false);
logException(LOG, INFO, e); }
return REJECT;
} }
db.setMessageState(txn, m.getId(), DELIVERED);
return new DeliveryResult(true, shareMsg);
} }
@DatabaseExecutor @DatabaseExecutor
@@ -458,4 +447,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
private static class DeliveryResult {
private final boolean valid, share;
private DeliveryResult(boolean valid, boolean share) {
this.valid = valid;
this.share = share;
}
}
} }

View File

@@ -51,6 +51,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
private final PluginConfig pluginConfig; private final PluginConfig pluginConfig;
private final TransportKeyManagerFactory transportKeyManagerFactory;
private final TransportCrypto transportCrypto; private final TransportCrypto transportCrypto;
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers; private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
@@ -60,39 +61,34 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
KeyManagerImpl(DatabaseComponent db, KeyManagerImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
PluginConfig pluginConfig, PluginConfig pluginConfig,
TransportCrypto transportCrypto, TransportKeyManagerFactory transportKeyManagerFactory,
TransportKeyManagerFactory transportKeyManagerFactory) { TransportCrypto transportCrypto) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.pluginConfig = pluginConfig; this.pluginConfig = pluginConfig;
this.transportKeyManagerFactory = transportKeyManagerFactory;
this.transportCrypto = transportCrypto; this.transportCrypto = transportCrypto;
managers = new ConcurrentHashMap<>(); managers = new ConcurrentHashMap<>();
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {
TransportKeyManager m = transportKeyManagerFactory.
createTransportKeyManager(f.getId(), f.getMaxLatency());
managers.put(f.getId(), m);
}
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) {
TransportKeyManager m = transportKeyManagerFactory.
createTransportKeyManager(f.getId(), f.getMaxLatency());
managers.put(f.getId(), m);
}
} }
@Override @Override
public void startService() throws ServiceException { public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
Map<TransportId, Integer> transports = new HashMap<>();
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
transports.put(f.getId(), f.getMaxLatency());
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
transports.put(f.getId(), f.getMaxLatency());
try { try {
db.transaction(false, txn -> { db.transaction(false, txn -> {
for (SimplexPluginFactory f : for (Entry<TransportId, Integer> e : transports.entrySet())
pluginConfig.getSimplexFactories()) { db.addTransport(txn, e.getKey(), e.getValue());
db.addTransport(txn, f.getId(), f.getMaxLatency()); for (Entry<TransportId, Integer> e : transports.entrySet()) {
managers.get(f.getId()).start(txn); TransportKeyManager m = transportKeyManagerFactory
} .createTransportKeyManager(e.getKey(),
for (DuplexPluginFactory f : e.getValue());
pluginConfig.getDuplexFactories()) { managers.put(e.getKey(), m);
db.addTransport(txn, f.getId(), f.getMaxLatency()); m.start(txn);
managers.get(f.getId()).start(txn);
} }
}); });
} catch (DbException e) { } catch (DbException e) {
@@ -105,17 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
} }
@Override @Override
public KeySetId addRotationKeys(Transaction txn, ContactId c, public Map<TransportId, KeySetId> addRotationKeys(
TransportId t, SecretKey rootKey, long timestamp, boolean alice, Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
boolean active) throws DbException { boolean alice, boolean active) throws DbException {
return withManager(t, m ->
m.addRotationKeys(txn, c, rootKey, timestamp, alice, active));
}
@Override
public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>(); Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) { for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey(); TransportId t = e.getKey();
@@ -149,7 +137,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair) PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException { throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto SecretKey staticMasterKey = transportCrypto
.deriveStaticMasterKey(theirPublicKey, ourKeyPair); .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
SecretKey rootKey = SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true); transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair); boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);

View File

@@ -24,21 +24,15 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
@Override @Override
public InputStream createStreamReader(InputStream in, StreamContext ctx) { public InputStream createStreamReader(InputStream in, StreamContext ctx) {
return new StreamReaderImpl(streamDecrypterFactory return new StreamReaderImpl(
.createStreamDecrypter(in, ctx)); streamDecrypterFactory.createStreamDecrypter(in, ctx));
} }
@Override @Override
public InputStream createContactExchangeStreamReader(InputStream in, public InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey) { SecretKey headerKey) {
return new StreamReaderImpl(streamDecrypterFactory return new StreamReaderImpl(
.createContactExchangeStreamDecrypter(in, headerKey)); streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
} headerKey));
@Override
public InputStream createLogStreamReader(InputStream in,
SecretKey headerKey) {
return new StreamReaderImpl(streamDecrypterFactory
.createLogStreamDecrypter(in, headerKey));
} }
} }

View File

@@ -26,21 +26,15 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
@Override @Override
public StreamWriter createStreamWriter(OutputStream out, public StreamWriter createStreamWriter(OutputStream out,
StreamContext ctx) { StreamContext ctx) {
return new StreamWriterImpl(streamEncrypterFactory return new StreamWriterImpl(
.createStreamEncrypter(out, ctx)); streamEncrypterFactory.createStreamEncrypter(out, ctx));
} }
@Override @Override
public StreamWriter createContactExchangeStreamWriter(OutputStream out, public StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey) { SecretKey headerKey) {
return new StreamWriterImpl(streamEncrypterFactory return new StreamWriterImpl(
.createContactExchangeStreamEncrypter(out, headerKey)); streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
} headerKey));
@Override
public StreamWriter createLogStreamWriter(OutputStream out,
SecretKey headerKey) {
return new StreamWriterImpl(streamEncrypterFactory
.createLogStreamEncrypter(out, headerKey));
} }
} }

View File

@@ -1,22 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
@NotNullByDefault
interface MessageEncoder {
Message encodeKeyMessage(GroupId contactGroupId,
TransportId transportId, PublicKey publicKey);
Message encodeActivateMessage(GroupId contactGroupId,
TransportId transportId, MessageId previousMessageId);
BdfDictionary encodeMessageMetadata(TransportId transportId,
MessageType type, boolean local);
}

View File

@@ -1,77 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.crypto.PublicKey;
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.transport.agreement.MessageType.ACTIVATE;
import static org.briarproject.bramble.transport.agreement.MessageType.KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
@Immutable
@NotNullByDefault
class MessageEncoderImpl implements MessageEncoder {
private final ClientHelper clientHelper;
private final Clock clock;
@Inject
MessageEncoderImpl(ClientHelper clientHelper, Clock clock) {
this.clientHelper = clientHelper;
this.clock = clock;
}
@Override
public Message encodeKeyMessage(GroupId contactGroupId,
TransportId transportId, PublicKey publicKey) {
BdfList body = BdfList.of(
KEY.getValue(),
transportId.getString(),
publicKey.getEncoded());
return encodeMessage(contactGroupId, body);
}
@Override
public Message encodeActivateMessage(GroupId contactGroupId,
TransportId transportId, MessageId previousMessageId) {
BdfList body = BdfList.of(
ACTIVATE.getValue(),
transportId.getString(),
previousMessageId);
return encodeMessage(contactGroupId, body);
}
@Override
public BdfDictionary encodeMessageMetadata(TransportId transportId,
MessageType type, boolean local) {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_IS_SESSION, false),
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()),
new BdfEntry(MSG_KEY_MESSAGE_TYPE, type.getValue()),
new BdfEntry(MSG_KEY_LOCAL, local));
}
private Message encodeMessage(GroupId contactGroupId, BdfList body) {
try {
return clientHelper.createMessage(contactGroupId,
clock.currentTimeMillis(), clientHelper.toByteArray(body));
} catch (FormatException e) {
throw new AssertionError();
}
}
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum MessageType {
KEY(0),
ACTIVATE(1);
private final int value;
MessageType(int value) {
this.value = value;
}
int getValue() {
return value;
}
static MessageType fromValue(int value) throws FormatException {
for (MessageType t : values()) if (t.value == value) return t;
throw new FormatException();
}
}

View File

@@ -1,58 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class Session {
private final State state;
@Nullable
private final MessageId lastLocalMessageId;
@Nullable
private final KeyPair localKeyPair;
@Nullable
private final Long localTimestamp;
@Nullable
private final KeySetId keySetId;
Session(State state, @Nullable MessageId lastLocalMessageId,
@Nullable KeyPair localKeyPair, @Nullable Long localTimestamp,
@Nullable KeySetId keySetId) {
this.state = state;
this.lastLocalMessageId = lastLocalMessageId;
this.localKeyPair = localKeyPair;
this.localTimestamp = localTimestamp;
this.keySetId = keySetId;
}
State getState() {
return state;
}
@Nullable
MessageId getLastLocalMessageId() {
return lastLocalMessageId;
}
@Nullable
KeyPair getLocalKeyPair() {
return localKeyPair;
}
@Nullable
Long getLocalTimestamp() {
return localTimestamp;
}
@Nullable
KeySetId getKeySetId() {
return keySetId;
}
}

View File

@@ -1,13 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@NotNullByDefault
interface SessionEncoder {
BdfDictionary encodeSession(Session s, TransportId transportId);
BdfDictionary getSessionQuery(TransportId transportId);
}

View File

@@ -1,68 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_IS_SESSION;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionEncoderImpl implements SessionEncoder {
@Inject
SessionEncoderImpl() {
}
@Override
public BdfDictionary encodeSession(Session s, TransportId transportId) {
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_IS_SESSION, true);
meta.put(MSG_KEY_TRANSPORT_ID, transportId.getString());
meta.put(SESSION_KEY_STATE, s.getState().getValue());
putNullable(meta, SESSION_KEY_LAST_LOCAL_MESSAGE_ID,
s.getLastLocalMessageId());
KeyPair localKeyPair = s.getLocalKeyPair();
if (localKeyPair == null) {
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY, NULL_VALUE);
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY, NULL_VALUE);
} else {
meta.put(SESSION_KEY_LOCAL_PUBLIC_KEY,
localKeyPair.getPublic().getEncoded());
meta.put(SESSION_KEY_LOCAL_PRIVATE_KEY,
localKeyPair.getPrivate().getEncoded());
}
putNullable(meta, SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
KeySetId keySetId = s.getKeySetId();
if (keySetId == null) meta.put(SESSION_KEY_KEY_SET_ID, NULL_VALUE);
else meta.put(SESSION_KEY_KEY_SET_ID, keySetId.getInt());
return meta;
}
@Override
public BdfDictionary getSessionQuery(TransportId transportId) {
return BdfDictionary.of(
new BdfEntry(MSG_KEY_IS_SESSION, true),
new BdfEntry(MSG_KEY_TRANSPORT_ID, transportId.getString()));
}
private void putNullable(BdfDictionary meta, String key,
@Nullable Object o) {
meta.put(key, o == null ? NULL_VALUE : o);
}
}

View File

@@ -1,11 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface SessionParser {
Session parseSession(BdfDictionary meta) throws FormatException;
}

View File

@@ -1,67 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.transport.KeySetId;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_KEY_SET_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PRIVATE_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_PUBLIC_KEY;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_LOCAL_TIMESTAMP;
import static org.briarproject.bramble.transport.agreement.TransportKeyAgreementConstants.SESSION_KEY_STATE;
@Immutable
@NotNullByDefault
class SessionParserImpl implements SessionParser {
private final TransportKeyAgreementCrypto crypto;
@Inject
SessionParserImpl(TransportKeyAgreementCrypto crypto) {
this.crypto = crypto;
}
@Override
public Session parseSession(BdfDictionary meta) throws FormatException {
State state =
State.fromValue(meta.getLong(SESSION_KEY_STATE).intValue());
MessageId lastLocalMessageId = null;
byte[] lastLocalMessageIdBytes =
meta.getOptionalRaw(SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
if (lastLocalMessageIdBytes != null) {
lastLocalMessageId = new MessageId(lastLocalMessageIdBytes);
}
KeyPair localKeyPair = null;
byte[] localPublicKeyBytes =
meta.getOptionalRaw(SESSION_KEY_LOCAL_PUBLIC_KEY);
byte[] localPrivateKeyBytes =
meta.getOptionalRaw(SESSION_KEY_LOCAL_PRIVATE_KEY);
if (localPublicKeyBytes != null && localPrivateKeyBytes != null) {
PublicKey pub = crypto.parsePublicKey(localPublicKeyBytes);
PrivateKey priv = crypto.parsePrivateKey(localPrivateKeyBytes);
localKeyPair = new KeyPair(pub, priv);
}
Long localTimestamp = meta.getOptionalLong(SESSION_KEY_LOCAL_TIMESTAMP);
KeySetId keySetId = null;
Long keySetIdLong = meta.getOptionalLong(SESSION_KEY_KEY_SET_ID);
if (keySetIdLong != null) {
keySetId = new KeySetId(keySetIdLong.intValue());
}
return new Session(state, lastLocalMessageId, localKeyPair,
localTimestamp, keySetId);
}
}

View File

@@ -1,43 +0,0 @@
package org.briarproject.bramble.transport.agreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
enum State {
/**
* We've sent a key message and are awaiting the contact's key message.
*/
AWAIT_KEY(0),
/**
* We've exchanged key messages, derived the transport keys and sent an
* activate message, and now we're awaiting the contact's activate message.
*/
AWAIT_ACTIVATE(1),
/**
* We've exchanged key messages and activate messages, and have derived and
* activated the transport keys. This is the end state.
*/
ACTIVATED(2);
private final int value;
State(int value) {
this.value = value;
}
int getValue() {
return value;
}
static State fromValue(int value) throws FormatException {
for (State s : values()) if (s.value == value) return s;
throw new FormatException();
}
}

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