mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
9 Commits
beta-0.16.
...
299-blueto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e9153bd81 | ||
|
|
15fa44c1b1 | ||
|
|
7ffe1b8bae | ||
|
|
bb8713ddcb | ||
|
|
1e9afd14db | ||
|
|
86ea6eae63 | ||
|
|
56ba639084 | ||
|
|
8c16c2107f | ||
|
|
cefe2b09e0 |
@@ -6,18 +6,8 @@ cache:
|
||||
- .gradle/caches
|
||||
|
||||
before_script:
|
||||
- set -e
|
||||
- export GRADLE_USER_HOME=$PWD/.gradle
|
||||
# Accept the license for the Android build tools
|
||||
- echo y | /opt/android-sdk/tools/bin/sdkmanager "build-tools;26.0.2"
|
||||
# Download OpenJDK 6 so we can compile against its standard library
|
||||
- JDK_FILE=openjdk-6-jre-headless_6b38-1.13.10-1~deb7u1_amd64.deb
|
||||
- if [ ! -d openjdk ]
|
||||
- then
|
||||
- wget -q http://ftp.uk.debian.org/debian/pool/main/o/openjdk-6/$JDK_FILE
|
||||
- dpkg-deb -x $JDK_FILE openjdk
|
||||
- fi
|
||||
- export JAVA_6_HOME=$PWD/openjdk/usr/lib/jvm/java-6-openjdk-amd64
|
||||
- echo y | /opt/android-sdk/tools/bin/sdkmanager "build-tools;23.0.3"
|
||||
|
||||
test:
|
||||
script:
|
||||
|
||||
@@ -6,90 +6,99 @@ apply plugin: 'witness'
|
||||
apply plugin: 'de.undercouch.download'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '26.0.2'
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '25.0.0'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 1619
|
||||
versionName "0.16.19"
|
||||
targetSdkVersion 22
|
||||
versionCode 1611
|
||||
versionName "0.16.11"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
|
||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
compile project(path: ':bramble-core', configuration: 'default')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
provided 'javax.annotation:jsr250-api:1.0'
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
|
||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
|
||||
'org.jacoco:org.jacoco.ant:0.7.4.201502262128:org.jacoco.ant-0.7.4.201502262128.jar:013ce2a68ba57a3c59215ae0dec4df3498c078062a38c3b94c841fc14450f283',
|
||||
'org.jacoco:org.jacoco.core:0.7.4.201502262128:org.jacoco.core-0.7.4.201502262128.jar:ec4c74554312fac5116350164786f91b35c9e082fa4ea598bfa42b5db05d7abb',
|
||||
'org.jacoco:org.jacoco.report:0.7.4.201502262128:org.jacoco.report-0.7.4.201502262128.jar:7a3554c605e088e7e323b1084656243f0444fa353e2f2dee1f1a4204eb64ff09',
|
||||
'org.ow2.asm:asm-debug-all:5.0.1:asm-debug-all-5.0.1.jar:4734de5b515a454b0096db6971fb068e5f70e6f10bbee2b3bd2fdfe5d978ed57',
|
||||
]
|
||||
def torBinaryDir = 'src/main/res/raw'
|
||||
|
||||
task downloadTorGeoIp(type: Download) {
|
||||
src 'https://briarproject.org/build/geoip-2017-09-06.zip'
|
||||
dest "$torBinaryDir/geoip.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
ext.torBinaryDir = 'src/main/res/raw'
|
||||
ext.torVersion = '0.2.9.14'
|
||||
ext.geoipVersion = '2017-11-06'
|
||||
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
||||
|
||||
def torBinaries = [
|
||||
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
|
||||
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
|
||||
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
|
||||
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
|
||||
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||
]
|
||||
|
||||
def downloadBinary(name) {
|
||||
return tasks.create("downloadBinary${name}", Download) {
|
||||
src "${torDownloadUrl}${name}.zip"
|
||||
.replace('tor_', "tor-${torVersion}-")
|
||||
.replace('geoip', "geoip-${geoipVersion}")
|
||||
.replaceAll('_', '-')
|
||||
dest "${torBinaryDir}/${name}.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
task downloadTorBinaryArm(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-arm.zip'
|
||||
dest "$torBinaryDir/tor_arm.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
def verifyBinary(name, chksum) {
|
||||
return tasks.create([
|
||||
name : "verifyBinary${name}",
|
||||
type : Verify,
|
||||
dependsOn: downloadBinary(name)]) {
|
||||
src "${torBinaryDir}/${name}.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum chksum
|
||||
}
|
||||
task downloadTorBinaryArmPie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-arm-pie.zip'
|
||||
dest "$torBinaryDir/tor_arm_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-x86.zip'
|
||||
dest "$torBinaryDir/tor_x86.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task downloadTorBinaryX86Pie(type: Download) {
|
||||
src 'https://briarproject.org/build/tor-0.2.9.12-x86-pie.zip'
|
||||
dest "$torBinaryDir/tor_x86_pie.zip"
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
||||
task verifyTorGeoIp(type: Verify, dependsOn: 'downloadTorGeoIp') {
|
||||
src "$torBinaryDir/geoip.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
|
||||
src "$torBinaryDir/tor_arm.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688'
|
||||
}
|
||||
|
||||
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
|
||||
src "$torBinaryDir/tor_arm_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
|
||||
src "$torBinaryDir/tor_x86.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab'
|
||||
}
|
||||
|
||||
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
|
||||
src "$torBinaryDir/tor_x86_pie.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288'
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
torBinaries.every { key, value ->
|
||||
preBuild.dependsOn.add(verifyBinary(key, value))
|
||||
preBuild.dependsOn {
|
||||
[
|
||||
'verifyTorGeoIp',
|
||||
'verifyTorBinaryArm',
|
||||
'verifyTorBinaryArmPie',
|
||||
'verifyTorBinaryX86',
|
||||
'verifyTorBinaryX86Pie'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||
|
||||
@@ -23,7 +22,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
@@ -35,21 +33,19 @@ public class AndroidPluginModule {
|
||||
|
||||
@Provides
|
||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler,
|
||||
AndroidExecutor androidExecutor, SecureRandom random,
|
||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||
Application app, LocationUtils locationUtils, DevReporter reporter,
|
||||
EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth =
|
||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||
appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||
appContext, locationUtils, reporter, eventBus,
|
||||
torSocketFactory, backoffFactory);
|
||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||
androidExecutor, appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
||||
locationUtils, reporter, eventBus, torSocketFactory,
|
||||
backoffFactory);
|
||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||
backoffFactory, appContext);
|
||||
Collection<DuplexPluginFactory> duplex =
|
||||
final Collection<DuplexPluginFactory> duplex =
|
||||
Arrays.asList(bluetooth, tor, lan);
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
||||
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
|
||||
private volatile boolean wasEnabledByUs = false;
|
||||
private volatile BluetoothStateReceiver receiver = null;
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
super.start();
|
||||
// Listen for changes to the Bluetooth state
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||
receiver = new BluetoothStateReceiver();
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialiseAdapter() throws IOException {
|
||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||
// with a message queue, so submit it to the AndroidExecutor
|
||||
try {
|
||||
adapter = androidExecutor.runOnBackgroundThread(
|
||||
BluetoothAdapter::getDefaultAdapter).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
if (adapter == null)
|
||||
throw new IOException("Bluetooth is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAdapterEnabled() {
|
||||
return adapter != null && adapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
void enableAdapter() {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void disableAdapterIfEnabledByUs() {
|
||||
if (isAdapterEnabled() && wasEnabledByUs) {
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
else LOG.info("Could not disable Bluetooth");
|
||||
wasEnabledByUs = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setEnabledByUs() {
|
||||
wasEnabledByUs = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
String getBluetoothAddress() {
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
||||
return address.isEmpty() ? null : address;
|
||||
}
|
||||
|
||||
@Override
|
||||
BluetoothServerSocket openServerSocket(String uuid) throws IOException {
|
||||
return adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", UUID.fromString(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
|
||||
throws IOException {
|
||||
return wrapSocket(ss.accept());
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||
return new AndroidBluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isValidAddress(String address) {
|
||||
return BluetoothAdapter.checkBluetoothAddress(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException {
|
||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||
UUID u = UUID.fromString(uuid);
|
||||
BluetoothSocket s = null;
|
||||
try {
|
||||
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
||||
s.connect();
|
||||
return wrapSocket(s);
|
||||
} catch (IOException e) {
|
||||
tryToClose(s);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Closeable c) {
|
||||
try {
|
||||
if (c != null) c.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) onAdapterEnabled();
|
||||
else if (state == STATE_OFF) onAdapterDisabled();
|
||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||
if (scanMode == SCAN_MODE_NONE) {
|
||||
LOG.info("Scan mode: None");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
||||
LOG.info("Scan mode: Connectable");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||
LOG.info("Scan mode: Discoverable");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
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.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
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.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
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.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class DroidtoothPlugin implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxLatency;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile boolean wasEnabledByUs = false;
|
||||
private volatile ArrayList<BluetoothEnableDisableReason>
|
||||
enableDisableReasons = new ArrayList<>();
|
||||
private volatile BluetoothStateReceiver receiver = null;
|
||||
private volatile BluetoothServerSocket socket = null;
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
this.callback = callback;
|
||||
this.maxLatency = maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
// Bluetooth detects dead connections so we don't need keepalives
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||
// with a message queue, so submit it to the AndroidExecutor
|
||||
try {
|
||||
adapter = androidExecutor.runOnBackgroundThread(
|
||||
new Callable<BluetoothAdapter>() {
|
||||
@Override
|
||||
public BluetoothAdapter call() throws Exception {
|
||||
return BluetoothAdapter.getDefaultAdapter();
|
||||
}
|
||||
}).get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.warning("Interrupted while getting BluetoothAdapter");
|
||||
throw new PluginException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
if (adapter == null) {
|
||||
LOG.info("Bluetooth is not supported");
|
||||
throw new PluginException();
|
||||
}
|
||||
running = true;
|
||||
// Listen for changes to the Bluetooth state
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||
receiver = new BluetoothStateReceiver();
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
// If Bluetooth is enabled, bind a socket
|
||||
if (adapter.isEnabled()) {
|
||||
bind();
|
||||
} else {
|
||||
// Enable Bluetooth if settings allow
|
||||
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, true)) {
|
||||
enableAdapter(BluetoothEnableDisableReason.COMMUNICATION);
|
||||
} else {
|
||||
LOG.info("Not enabling Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning()) return;
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext,
|
||||
adapter);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
if (!StringUtils.isNullOrEmpty(address)) {
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ADDRESS, address);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
// Bind a server socket to accept connections from contacts
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", getUuid());
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
LOG.info("Socket bound");
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private UUID getUuid() {
|
||||
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
||||
if (uuid == null) {
|
||||
byte[] random = new byte[UUID_BYTES];
|
||||
secureRandom.nextBytes(random);
|
||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_UUID, uuid);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
return UUID.fromString(uuid);
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
callback.transportDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptContactConnections() {
|
||||
while (isRunning()) {
|
||||
BluetoothSocket s;
|
||||
try {
|
||||
s = socket.accept();
|
||||
} catch (IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
return;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
String address = s.getRemoteDevice().getAddress();
|
||||
LOG.info("Connection from " + scrubMacAddress(address));
|
||||
}
|
||||
backoff.reset();
|
||||
callback.incomingConnectionCreated(wrapSocket(s));
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
private void enableAdapter(BluetoothEnableDisableReason reason) {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
|
||||
if(!enableDisableReasons.contains(reason)) {
|
||||
enableDisableReasons.add(reason);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
tryToClose(socket);
|
||||
disableAdapter(true);
|
||||
}
|
||||
|
||||
private void disableAdapter(boolean force){
|
||||
disableAdapter(null, force);
|
||||
}
|
||||
|
||||
private void disableAdapter(BluetoothEnableDisableReason reason,
|
||||
boolean force) {
|
||||
if (adapter != null && adapter.isEnabled() && wasEnabledByUs
|
||||
&& (enableDisableReasons.contains(reason) || force)) {
|
||||
|
||||
if(enableDisableReasons.contains(reason)){
|
||||
enableDisableReasons.remove(reason);
|
||||
}
|
||||
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
else LOG.info("Could not disable Bluetooth");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running && adapter != null && adapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!isRunning()) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
final ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
final String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
final String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BluetoothSocket connect(String address, String uuid) {
|
||||
// Validate the address
|
||||
if (!BluetoothAdapter.checkBluetoothAddress(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
|
||||
UUID u;
|
||||
try {
|
||||
u = UUID.fromString(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
||||
return null;
|
||||
}
|
||||
// Try to connect
|
||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||
BluetoothSocket s = null;
|
||||
try {
|
||||
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||
s.connect();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connected to " + scrubMacAddress(address));
|
||||
return s;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Failed to connect to " + scrubMacAddress(address)
|
||||
+ ": " + e);
|
||||
}
|
||||
tryToClose(s);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Closeable c) {
|
||||
try {
|
||||
if (c != null) c.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s == null) return null;
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (!isRunning()) return null;
|
||||
// There's no point listening if we can't discover our own address
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
||||
if (address.isEmpty()) return null;
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
descriptor.add(StringUtils.macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
if (!isRunning()) return null;
|
||||
String address;
|
||||
try {
|
||||
address = parseAddress(descriptor);
|
||||
} catch (FormatException e) {
|
||||
LOG.info("Invalid address in key agreement descriptor");
|
||||
return null;
|
||||
}
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
BluetoothSocket s = connect(address, uuid.toString());
|
||||
if (s == null) return null;
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
byte[] mac = descriptor.getRaw(1);
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
return StringUtils.macToString(mac);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof EnableBluetoothEvent) {
|
||||
EnableBluetoothEvent enable = (EnableBluetoothEvent) e;
|
||||
enableAdapterAsync(enable.getReason());
|
||||
} else if (e instanceof DisableBluetoothEvent) {
|
||||
DisableBluetoothEvent disable = (DisableBluetoothEvent) e;
|
||||
disableAdapterAsync(disable.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
private void enableAdapterAsync(final BluetoothEnableDisableReason reason) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
enableAdapter(reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void disableAdapterAsync(final BluetoothEnableDisableReason reason) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
disableAdapter(reason, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) {
|
||||
LOG.info("Bluetooth enabled");
|
||||
bind();
|
||||
} else if (state == STATE_OFF) {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
}
|
||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||
if (scanMode == SCAN_MODE_NONE) {
|
||||
LOG.info("Scan mode: None");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
||||
LOG.info("Scan mode: Connectable");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||
LOG.info("Scan mode: Discoverable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final BluetoothServerSocket ss;
|
||||
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor,
|
||||
BluetoothServerSocket ss) {
|
||||
super(descriptor);
|
||||
this.ss = ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return new Callable<KeyAgreementConnection>() {
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws IOException {
|
||||
BluetoothSocket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new DroidtoothTransportConnection(
|
||||
DroidtoothPlugin.this, s), ID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||
@@ -35,7 +35,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
private final EventBus eventBus;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
||||
public DroidtoothPluginFactory(Executor ioExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
@@ -61,7 +61,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
||||
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
|
||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
|
||||
@@ -11,12 +11,11 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
class AndroidBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
class DroidtoothTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final BluetoothSocket socket;
|
||||
|
||||
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
super(plugin);
|
||||
this.socket = socket;
|
||||
}
|
||||
@@ -59,12 +59,7 @@ import java.util.Map.Entry;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@@ -75,15 +70,10 @@ import javax.net.SocketFactory;
|
||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||
@@ -112,7 +102,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
Logger.getLogger(TorPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
private final DevReporter reporter;
|
||||
@@ -125,9 +114,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||
private final File doneFile, cookieFile;
|
||||
private final PowerManager.WakeLock wakeLock;
|
||||
private final Lock connectionStatusLock;
|
||||
private final AtomicReference<Future<?>> connectivityCheck =
|
||||
new AtomicReference<>();
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
@@ -136,13 +122,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private volatile TorControlConnection controlConnection = null;
|
||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||
|
||||
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||
Context appContext, LocationUtils locationUtils,
|
||||
DevReporter reporter, SocketFactory torSocketFactory,
|
||||
Backoff backoff, DuplexPluginCallback callback,
|
||||
String architecture, int maxLatency, int maxIdleTime) {
|
||||
TorPlugin(Executor ioExecutor, Context appContext,
|
||||
LocationUtils locationUtils, DevReporter reporter,
|
||||
SocketFactory torSocketFactory, Backoff backoff,
|
||||
DuplexPluginCallback callback, String architecture, int maxLatency,
|
||||
int maxIdleTime) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.appContext = appContext;
|
||||
this.locationUtils = locationUtils;
|
||||
this.reporter = reporter;
|
||||
@@ -167,7 +152,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
// This tag will prevent Huawei's powermanager from killing us.
|
||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||
wakeLock.setReferenceCounted(false);
|
||||
connectionStatusLock = new ReentrantLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,11 +204,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
||||
if (stdout.hasNextLine()) {
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()){
|
||||
if(stdout.hasNextLine()) {
|
||||
LOG.info(stdout.nextLine());
|
||||
}
|
||||
if (stderr.hasNextLine()) {
|
||||
if(stderr.hasNextLine()){
|
||||
LOG.info(stderr.nextLine());
|
||||
}
|
||||
}
|
||||
@@ -273,11 +257,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
// Register to receive network status events
|
||||
networkStateReceiver = new NetworkStateReceiver();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(CONNECTIVITY_ACTION);
|
||||
filter.addAction(ACTION_SCREEN_ON);
|
||||
filter.addAction(ACTION_SCREEN_OFF);
|
||||
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
||||
appContext.registerReceiver(networkStateReceiver, filter);
|
||||
// Bind a server socket to receive incoming hidden service connections
|
||||
bind();
|
||||
@@ -390,45 +370,57 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private void sendDevReports() {
|
||||
ioExecutor.execute(() -> {
|
||||
// TODO: Trigger this with a TransportEnabledEvent
|
||||
File reportDir = AndroidUtils.getReportDir(appContext);
|
||||
reporter.sendReports(reportDir);
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO: Trigger this with a TransportEnabledEvent
|
||||
File reportDir = AndroidUtils.getReportDir(appContext);
|
||||
reporter.sendReports(reportDir);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
String portString = callback.getSettings().get(PREF_TOR_PORT);
|
||||
int port;
|
||||
if (StringUtils.isNullOrEmpty(portString)) port = 0;
|
||||
else port = Integer.parseInt(portString);
|
||||
// Bind a server socket to receive connections from Tor
|
||||
ServerSocket ss = null;
|
||||
try {
|
||||
ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress("127.0.0.1", port));
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
tryToClose(ss);
|
||||
return;
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
String portString = callback.getSettings().get(PREF_TOR_PORT);
|
||||
int port;
|
||||
if (StringUtils.isNullOrEmpty(portString)) port = 0;
|
||||
else port = Integer.parseInt(portString);
|
||||
// Bind a server socket to receive connections from Tor
|
||||
ServerSocket ss = null;
|
||||
try {
|
||||
ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress("127.0.0.1", port));
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
// Store the port number
|
||||
final String localPort = String.valueOf(ss.getLocalPort());
|
||||
Settings s = new Settings();
|
||||
s.put(PREF_TOR_PORT, localPort);
|
||||
callback.mergeSettings(s);
|
||||
// Create a hidden service if necessary
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
publishHiddenService(localPort);
|
||||
}
|
||||
});
|
||||
backoff.reset();
|
||||
// Accept incoming hidden service connections from Tor
|
||||
acceptContactConnections(ss);
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
// Store the port number
|
||||
String localPort = String.valueOf(ss.getLocalPort());
|
||||
Settings s = new Settings();
|
||||
s.put(PREF_TOR_PORT, localPort);
|
||||
callback.mergeSettings(s);
|
||||
// Create a hidden service if necessary
|
||||
ioExecutor.execute(() -> publishHiddenService(localPort));
|
||||
backoff.reset();
|
||||
// Accept incoming hidden service connections from Tor
|
||||
acceptContactConnections(ss);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -556,13 +548,17 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndCallBack(ContactId c, TransportProperties p) {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
private void connectAndCallBack(final ContactId c,
|
||||
final TransportProperties p) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -614,7 +610,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -638,8 +634,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||
if (status.equals("CLOSED") || status.equals("FAILED"))
|
||||
updateConnectionStatus(); // Check whether we've lost connectivity
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -679,7 +673,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event, @Nullable String path) {
|
||||
public void onEvent(int event, String path) {
|
||||
stopWatching();
|
||||
latch.countDown();
|
||||
}
|
||||
@@ -697,75 +691,60 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private void updateConnectionStatus() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
try {
|
||||
connectionStatusLock.lock();
|
||||
updateConnectionStatusLocked();
|
||||
} finally {
|
||||
connectionStatusLock.unlock();
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
|
||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
boolean online = net != null && net.isConnected();
|
||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||
String country = locationUtils.getCurrentCountry();
|
||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||
country);
|
||||
Settings s = callback.getSettings();
|
||||
int network = s.getInt(PREF_TOR_NETWORK,
|
||||
PREF_TOR_NETWORK_ALWAYS);
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
if ("".equals(country)) LOG.info("Country code unknown");
|
||||
else LOG.info("Country code: " + country);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
enableNetwork(false);
|
||||
} else if (blocked) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
enableNetwork(false);
|
||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||
LOG.info("Disabling network due to data setting");
|
||||
enableNetwork(false);
|
||||
} else {
|
||||
LOG.info("Enabling network");
|
||||
enableNetwork(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Locking: connectionStatusLock
|
||||
private void updateConnectionStatusLocked() {
|
||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
boolean online = net != null && net.isConnected();
|
||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||
String country = locationUtils.getCurrentCountry();
|
||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||
country);
|
||||
Settings s = callback.getSettings();
|
||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
if ("".equals(country)) LOG.info("Country code unknown");
|
||||
else LOG.info("Country code: " + country);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
enableNetwork(false);
|
||||
} else if (blocked) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
enableNetwork(false);
|
||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||
LOG.info("Disabling network due to data setting");
|
||||
enableNetwork(false);
|
||||
} else {
|
||||
LOG.info("Enabling network");
|
||||
enableNetwork(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleConnectionStatusUpdate() {
|
||||
Future<?> newConnectivityCheck =
|
||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||
Future<?> oldConnectivityCheck =
|
||||
connectivityCheck.getAndSet(newConnectivityCheck);
|
||||
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
|
||||
}
|
||||
|
||||
private class NetworkStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent i) {
|
||||
if (!running) return;
|
||||
String action = i.getAction();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
|
||||
updateConnectionStatus();
|
||||
if (ACTION_SCREEN_ON.equals(action)
|
||||
|| ACTION_SCREEN_OFF.equals(action)) {
|
||||
scheduleConnectionStatusUpdate();
|
||||
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
||||
LOG.info("Detected connectivity change");
|
||||
updateConnectionStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
@@ -37,7 +36,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
private final DevReporter reporter;
|
||||
@@ -45,13 +43,11 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
private final SocketFactory torSocketFactory;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public TorPluginFactory(Executor ioExecutor,
|
||||
ScheduledExecutorService scheduler, Context appContext,
|
||||
public TorPluginFactory(Executor ioExecutor, Context appContext,
|
||||
LocationUtils locationUtils, DevReporter reporter,
|
||||
EventBus eventBus, SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.appContext = appContext;
|
||||
this.locationUtils = locationUtils;
|
||||
this.reporter = reporter;
|
||||
@@ -93,9 +89,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
||||
locationUtils, reporter, torSocketFactory, backoff, callback,
|
||||
architecture, MAX_LATENCY, MAX_IDLE_TIME);
|
||||
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
|
||||
reporter, torSocketFactory, backoff, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@@ -27,11 +27,14 @@ class AndroidExecutorImpl implements AndroidExecutor {
|
||||
@Inject
|
||||
AndroidExecutorImpl(Application app) {
|
||||
uiHandler = new Handler(app.getApplicationContext().getMainLooper());
|
||||
loop = () -> {
|
||||
Looper.prepare();
|
||||
backgroundHandler = new Handler();
|
||||
startLatch.countDown();
|
||||
Looper.loop();
|
||||
loop = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
backgroundHandler = new Handler();
|
||||
startLatch.countDown();
|
||||
Looper.loop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
apply plugin: 'java-library'
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
apply plugin: 'java'
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
|
||||
apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.dagger:dagger:2.0.2"
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
compile "com.google.dagger:dagger:2.0.2"
|
||||
compile 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
compile 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jmock:jmock:2.8.2"
|
||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
||||
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
||||
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.jmock:jmock:2.8.2"
|
||||
testCompile "org.jmock:jmock-junit4:2.8.2"
|
||||
testCompile "org.jmock:jmock-legacy:2.8.2"
|
||||
testCompile "org.hamcrest:hamcrest-library:1.3"
|
||||
testCompile "org.hamcrest:hamcrest-core:1.3"
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
||||
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
|
||||
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
|
||||
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
|
||||
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
||||
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
||||
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
||||
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
||||
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||
'com.google.code.findbugs:jsr305:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -48,8 +39,3 @@ task jarTest(type: Jar, dependsOn: testClasses) {
|
||||
artifacts {
|
||||
testOutput jarTest
|
||||
}
|
||||
|
||||
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
|
||||
tasks.withType(JavaCompile) {
|
||||
useJava6StandardLibrary(it)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class BdfMessageContext {
|
||||
}
|
||||
|
||||
public BdfMessageContext(BdfDictionary dictionary) {
|
||||
this(dictionary, Collections.emptyList());
|
||||
this(dictionary, Collections.<MessageId>emptyList());
|
||||
}
|
||||
|
||||
public BdfDictionary getDictionary() {
|
||||
|
||||
@@ -4,14 +4,11 @@ import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
public class BdfDictionary extends TreeMap<String, Object> {
|
||||
public class BdfDictionary extends ConcurrentSkipListMap<String, Object> {
|
||||
|
||||
public static final Object NULL_VALUE = new Object();
|
||||
|
||||
|
||||
@@ -3,17 +3,15 @@ package org.briarproject.bramble.api.data;
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
|
||||
|
||||
@NotThreadSafe
|
||||
public class BdfList extends ArrayList<Object> {
|
||||
public class BdfList extends Vector<Object> {
|
||||
|
||||
/**
|
||||
* Factory method for constructing lists inline.
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when the database uses a newer schema than the current code.
|
||||
*/
|
||||
public class DataTooNewException extends DbException {
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when the database uses an older schema than the current code and
|
||||
* cannot be migrated.
|
||||
*/
|
||||
public class DataTooOldException extends DbException {
|
||||
}
|
||||
@@ -37,13 +37,8 @@ public interface DatabaseComponent {
|
||||
|
||||
/**
|
||||
* Opens the database and returns true if the database already existed.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||
boolean open() throws DbException;
|
||||
|
||||
/**
|
||||
* Waits for any open transactions to finish and closes the database.
|
||||
@@ -127,9 +122,8 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Deletes the message with the given ID. Unlike
|
||||
* {@link #removeMessage(Transaction, MessageId)}, the message ID and any
|
||||
* other associated data are not deleted.
|
||||
* Deletes the message with the given ID. The message ID and any other
|
||||
* associated data are not deleted.
|
||||
*/
|
||||
void deleteMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
@@ -378,16 +372,6 @@ public interface DatabaseComponent {
|
||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
/*
|
||||
* 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
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
||||
* no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all settings in the given namespace.
|
||||
* <p/>
|
||||
@@ -468,11 +452,6 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a message (and all associated state) from the database.
|
||||
*/
|
||||
void removeMessage(Transaction txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a transport (and all associated state) from the database.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
import java.util.TreeMap;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
public class Metadata extends TreeMap<String, byte[]> {
|
||||
@ThreadSafe
|
||||
public class Metadata extends Hashtable<String, byte[]> {
|
||||
|
||||
/**
|
||||
* Special value to indicate that a key is being removed.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
public interface MigrationListener {
|
||||
|
||||
/**
|
||||
* This is called when a migration is started while opening the database.
|
||||
* It will be called once for each migration being applied.
|
||||
*/
|
||||
void onMigrationRun();
|
||||
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class Transaction {
|
||||
* committed.
|
||||
*/
|
||||
public void attach(Event e) {
|
||||
if (events == null) events = new ArrayList<>();
|
||||
if (events == null) events = new ArrayList<Event>();
|
||||
events.add(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* An class for managing a particular key agreement listener.
|
||||
@@ -24,11 +24,11 @@ public abstract class KeyAgreementListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until an incoming connection is received and returns it.
|
||||
*
|
||||
* @throws IOException if an error occurs or {@link #close()} is called.
|
||||
* Starts listening for incoming connections, and returns a Callable that
|
||||
* will return a KeyAgreementConnection when an incoming connection is
|
||||
* received.
|
||||
*/
|
||||
public abstract KeyAgreementConnection accept() throws IOException;
|
||||
public abstract Callable<KeyAgreementConnection> listen();
|
||||
|
||||
/**
|
||||
* Closes the underlying server socket.
|
||||
|
||||
@@ -21,25 +21,7 @@ public interface LifecycleManager {
|
||||
* The result of calling {@link #startServices(String)}.
|
||||
*/
|
||||
enum StartResult {
|
||||
ALREADY_RUNNING,
|
||||
DB_ERROR,
|
||||
DATA_TOO_OLD_ERROR,
|
||||
DATA_TOO_NEW_ERROR,
|
||||
SERVICE_ERROR,
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
/**
|
||||
* The state the lifecycle can be in.
|
||||
* Returned by {@link #getLifecycleState()}
|
||||
*/
|
||||
enum LifecycleState {
|
||||
|
||||
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
|
||||
|
||||
public boolean isAfter(LifecycleState state) {
|
||||
return ordinal() > state.ordinal();
|
||||
}
|
||||
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,10 +71,4 @@ public interface LifecycleManager {
|
||||
* the {@link DatabaseComponent} to be closed before returning.
|
||||
*/
|
||||
void waitForShutdown() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Returns the current state of the lifecycle.
|
||||
*/
|
||||
LifecycleState getLifecycleState();
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.briarproject.bramble.api.lifecycle.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the app enters a new lifecycle state.
|
||||
*/
|
||||
public class LifecycleEvent extends Event {
|
||||
|
||||
private final LifecycleState state;
|
||||
|
||||
public LifecycleEvent(LifecycleState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.bramble.api.lifecycle.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the app is shutting down.
|
||||
*/
|
||||
public class ShutdownEvent extends Event {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
public enum BluetoothEnableDisableReason {
|
||||
COMMUNICATION,
|
||||
ADD_CONTACT
|
||||
}
|
||||
@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Attempts to connect to the remote peer specified in the given descriptor.
|
||||
* Returns null if no connection can be established.
|
||||
* Returns null if no connection can be established within the given time.
|
||||
*/
|
||||
@Nullable
|
||||
DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] remoteCommitment, BdfList descriptor);
|
||||
byte[] remoteCommitment, BdfList descriptor, long timeout);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that informs the Bluetooth plugin that we have enabled the
|
||||
* Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class BluetoothEnabledEvent extends Event {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
|
||||
abstract class BluetoothEvent extends Event {
|
||||
private BluetoothEnableDisableReason selectedReason;
|
||||
|
||||
BluetoothEvent(BluetoothEnableDisableReason reason){
|
||||
selectedReason = reason;
|
||||
}
|
||||
|
||||
public BluetoothEnableDisableReason getReason(){
|
||||
return selectedReason;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@@ -11,5 +11,8 @@ import javax.annotation.concurrent.Immutable;
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class DisableBluetoothEvent extends Event {
|
||||
public class DisableBluetoothEvent extends BluetoothEvent {
|
||||
public DisableBluetoothEvent(BluetoothEnableDisableReason reason) {
|
||||
super(reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@ package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class EnableBluetoothEvent extends Event {
|
||||
public class EnableBluetoothEvent extends BluetoothEvent {
|
||||
public EnableBluetoothEvent(BluetoothEnableDisableReason reason){
|
||||
super(reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public interface TransportPropertyManager {
|
||||
/**
|
||||
* Returns the local transport properties for all transports.
|
||||
* <br/>
|
||||
* TODO: Transaction can be read-only when code is simplified
|
||||
* Read-Only
|
||||
*/
|
||||
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class MessageContext {
|
||||
}
|
||||
|
||||
public MessageContext(Metadata metadata) {
|
||||
this(metadata, Collections.emptyList());
|
||||
this(metadata, Collections.<MessageId>emptyList());
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
|
||||
@@ -126,10 +126,6 @@ public class StringUtils {
|
||||
return toUtf8(s).length > maxLength;
|
||||
}
|
||||
|
||||
public static boolean isValidMac(String mac) {
|
||||
return MAC.matcher(mac).matches();
|
||||
}
|
||||
|
||||
public static byte[] macToBytes(String mac) {
|
||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||
return fromHexString(mac.replaceAll(":", ""));
|
||||
|
||||
@@ -3,7 +3,8 @@ package org.briarproject.bramble.test;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.After;
|
||||
|
||||
public abstract class BrambleMockTestCase extends BrambleTestCase {
|
||||
public abstract class BrambleMockTestCase extends
|
||||
BrambleTestCase {
|
||||
|
||||
protected final Mockery context = new Mockery();
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ public abstract class BrambleTestCase {
|
||||
|
||||
public BrambleTestCase() {
|
||||
// Ensure exceptions thrown on worker threads cause tests to fail
|
||||
UncaughtExceptionHandler fail = (thread, throwable) -> {
|
||||
throwable.printStackTrace();
|
||||
fail();
|
||||
UncaughtExceptionHandler fail = new UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
fail();
|
||||
}
|
||||
};
|
||||
Thread.setDefaultUncaughtExceptionHandler(fail);
|
||||
}
|
||||
|
||||
@@ -2,27 +2,12 @@ package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
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.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
private static final AtomicInteger nextTestDir =
|
||||
@@ -53,50 +38,4 @@ public class TestUtils {
|
||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||
}
|
||||
|
||||
public static LocalAuthor getLocalAuthor() {
|
||||
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||
}
|
||||
|
||||
public static LocalAuthor getLocalAuthor(int nameLength) {
|
||||
AuthorId id = new AuthorId(getRandomId());
|
||||
String name = getRandomString(nameLength);
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
long created = System.currentTimeMillis();
|
||||
return new LocalAuthor(id, name, publicKey, privateKey, created);
|
||||
}
|
||||
|
||||
public static Author getAuthor() {
|
||||
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||
}
|
||||
|
||||
public static Author getAuthor(int nameLength) {
|
||||
AuthorId id = new AuthorId(getRandomId());
|
||||
String name = getRandomString(nameLength);
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
return new Author(id, name, publicKey);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId) {
|
||||
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||
return getGroup(clientId, descriptorLength);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
||||
GroupId groupId = new GroupId(getRandomId());
|
||||
byte[] descriptor = getRandomBytes(descriptorLength);
|
||||
return new Group(groupId, clientId, descriptor);
|
||||
}
|
||||
|
||||
public static Message getMessage(GroupId groupId) {
|
||||
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
|
||||
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
|
||||
}
|
||||
|
||||
public static Message getMessage(GroupId groupId, int rawLength) {
|
||||
MessageId id = new MessageId(getRandomId());
|
||||
byte[] raw = getRandomBytes(rawLength);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return new Message(id, groupId, timestamp, raw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,28 @@
|
||||
apply plugin: 'java-library'
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'net.ltgt.apt' version '0.9'
|
||||
id 'idea'
|
||||
}
|
||||
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
|
||||
apply plugin: 'net.ltgt.apt'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-api', configuration: 'default')
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
compile project(path: ':bramble-api', configuration: 'default')
|
||||
compile 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
compile 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
||||
compile 'org.bitlet:weupnp:0.1.4'
|
||||
|
||||
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
|
||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jmock:jmock:2.8.2"
|
||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
||||
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
||||
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
||||
|
||||
testApt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
testCompile project(path: ':bramble-api', configuration: 'testOutput')
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
|
||||
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
|
||||
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
|
||||
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
|
||||
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
|
||||
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
|
||||
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
||||
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
|
||||
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
|
||||
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
|
||||
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
||||
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
||||
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
||||
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
||||
'com.madgag.spongycastle:core:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'com.h2database:h2:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -63,8 +37,3 @@ task jarTest(type: Jar, dependsOn: testClasses) {
|
||||
artifacts {
|
||||
testOutput jarTest
|
||||
}
|
||||
|
||||
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
|
||||
tasks.withType(JavaCompile) {
|
||||
useJava6StandardLibrary(it)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class PoliteExecutor implements Executor {
|
||||
|
||||
private final Object lock = new Object();
|
||||
@GuardedBy("lock")
|
||||
private final Queue<Runnable> queue = new LinkedList<>();
|
||||
private final Queue<Runnable> queue = new LinkedList<Runnable>();
|
||||
private final Executor delegate;
|
||||
private final int maxConcurrentTasks;
|
||||
private final Logger log;
|
||||
@@ -48,17 +48,20 @@ public class PoliteExecutor implements Executor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable r) {
|
||||
long submitted = System.currentTimeMillis();
|
||||
Runnable wrapped = () -> {
|
||||
if (log.isLoggable(LOG_LEVEL)) {
|
||||
long queued = System.currentTimeMillis() - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
}
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
scheduleNext();
|
||||
public void execute(final Runnable r) {
|
||||
final long submitted = System.currentTimeMillis();
|
||||
Runnable wrapped = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (log.isLoggable(LOG_LEVEL)) {
|
||||
long queued = System.currentTimeMillis() - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
}
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
scheduleNext();
|
||||
}
|
||||
}
|
||||
};
|
||||
synchronized (lock) {
|
||||
|
||||
@@ -28,16 +28,19 @@ public class TimeLoggingExecutor extends ThreadPoolExecutor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable r) {
|
||||
public void execute(final Runnable r) {
|
||||
if (log.isLoggable(LOG_LEVEL)) {
|
||||
long submitted = System.currentTimeMillis();
|
||||
super.execute(() -> {
|
||||
long started = System.currentTimeMillis();
|
||||
long queued = started - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
r.run();
|
||||
long executing = System.currentTimeMillis() - started;
|
||||
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
|
||||
final long submitted = System.currentTimeMillis();
|
||||
super.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long started = System.currentTimeMillis();
|
||||
long queued = started - submitted;
|
||||
log.log(LOG_LEVEL, "Queue time " + queued + " ms");
|
||||
r.run();
|
||||
long executing = System.currentTimeMillis() - started;
|
||||
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
super.execute(r);
|
||||
|
||||
@@ -201,7 +201,8 @@ class ClientHelperImpl implements ClientHelper {
|
||||
public Map<MessageId, BdfDictionary> getMessageMetadataAsDictionary(
|
||||
Transaction txn, GroupId g) throws DbException, FormatException {
|
||||
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g);
|
||||
Map<MessageId, BdfDictionary> parsed = new HashMap<>(raw.size());
|
||||
Map<MessageId, BdfDictionary> parsed =
|
||||
new HashMap<MessageId, BdfDictionary>(raw.size());
|
||||
for (Entry<MessageId, Metadata> e : raw.entrySet())
|
||||
parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
|
||||
return parsed;
|
||||
@@ -228,7 +229,8 @@ class ClientHelperImpl implements ClientHelper {
|
||||
FormatException {
|
||||
Metadata metadata = metadataEncoder.encode(query);
|
||||
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g, metadata);
|
||||
Map<MessageId, BdfDictionary> parsed = new HashMap<>(raw.size());
|
||||
Map<MessageId, BdfDictionary> parsed =
|
||||
new HashMap<MessageId, BdfDictionary>(raw.size());
|
||||
for (Entry<MessageId, Metadata> e : raw.entrySet())
|
||||
parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
|
||||
return parsed;
|
||||
|
||||
@@ -184,7 +184,12 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
// Close the outgoing stream and expect EOF on the incoming stream
|
||||
w.close();
|
||||
if (!r.eof()) LOG.warning("Unexpected data at end of connection");
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
} catch (GeneralSecurityException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
listener.contactExchangeFailed();
|
||||
tryToClose(conn, true);
|
||||
@@ -271,7 +276,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
|
||||
private Map<TransportId, TransportProperties> receiveTransportProperties(
|
||||
BdfReader r) throws IOException {
|
||||
Map<TransportId, TransportProperties> remote = new HashMap<>();
|
||||
Map<TransportId, TransportProperties> remote =
|
||||
new HashMap<TransportId, TransportProperties>();
|
||||
r.readListStart();
|
||||
while (!r.hasListEnd()) {
|
||||
r.readListStart();
|
||||
|
||||
@@ -34,8 +34,8 @@ class ContactManagerImpl implements ContactManager {
|
||||
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
|
||||
this.db = db;
|
||||
this.keyManager = keyManager;
|
||||
addHooks = new CopyOnWriteArrayList<>();
|
||||
removeHooks = new CopyOnWriteArrayList<>();
|
||||
addHooks = new CopyOnWriteArrayList<AddContactHook>();
|
||||
removeHooks = new CopyOnWriteArrayList<RemoveContactHook>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,7 +125,7 @@ class ContactManagerImpl implements ContactManager {
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
List<Contact> active = new ArrayList<>(contacts.size());
|
||||
List<Contact> active = new ArrayList<Contact>(contacts.size());
|
||||
for (Contact c : contacts) if (c.isActive()) active.add(c);
|
||||
return active;
|
||||
}
|
||||
|
||||
@@ -602,8 +602,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
// Package access for testing
|
||||
int chooseIterationCount(int targetMillis) {
|
||||
List<Long> quickSamples = new ArrayList<>(PBKDF_SAMPLES);
|
||||
List<Long> slowSamples = new ArrayList<>(PBKDF_SAMPLES);
|
||||
List<Long> quickSamples = new ArrayList<Long>(PBKDF_SAMPLES);
|
||||
List<Long> slowSamples = new ArrayList<Long>(PBKDF_SAMPLES);
|
||||
long iterationNanos = 0, initNanos = 0;
|
||||
while (iterationNanos <= 0 || initNanos <= 0) {
|
||||
// Sample the running time with one iteration and two iterations
|
||||
|
||||
@@ -48,7 +48,7 @@ public class CryptoModule {
|
||||
|
||||
public CryptoModule() {
|
||||
// Use an unbounded queue
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
|
||||
@@ -16,7 +16,7 @@ class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
|
||||
|
||||
@Override
|
||||
public float estimateStrength(String password) {
|
||||
HashSet<Character> unique = new HashSet<>();
|
||||
HashSet<Character> unique = new HashSet<Character>();
|
||||
int length = password.length();
|
||||
for (int i = 0; i < length; i++) unique.add(password.charAt(i));
|
||||
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);
|
||||
|
||||
@@ -2,11 +2,8 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -40,13 +37,8 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Opens the database and returns true if the database already existed.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||
boolean open() throws DbException;
|
||||
|
||||
/**
|
||||
* Prevents new transactions from starting, waits for all current
|
||||
@@ -457,16 +449,6 @@ interface Database<T> {
|
||||
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
||||
* if no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the message with the given ID, in serialised form, or null if
|
||||
* the message has been deleted.
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||
@@ -91,6 +90,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
private final ReentrantReadWriteLock lock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
|
||||
private volatile int shutdownHandle = -1;
|
||||
|
||||
@Inject
|
||||
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
||||
ShutdownManager shutdown) {
|
||||
@@ -101,22 +102,27 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(@Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
boolean reopened = db.open(listener);
|
||||
shutdown.addShutdownHook(() -> {
|
||||
try {
|
||||
close();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
public boolean open() throws DbException {
|
||||
Runnable shutdownHook = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
close();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
boolean reopened = db.open();
|
||||
shutdownHandle = shutdown.addShutdownHook(shutdownHook);
|
||||
return reopened;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws DbException {
|
||||
if (closed.getAndSet(true)) return;
|
||||
shutdown.removeShutdownHook(shutdownHandle);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@@ -135,7 +141,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
try {
|
||||
return new Transaction(db.startTransaction(), readOnly);
|
||||
} catch (DbException | RuntimeException e) {
|
||||
} catch (DbException e) {
|
||||
if (readOnly) lock.readLock().unlock();
|
||||
else lock.writeLock().unlock();
|
||||
throw e;
|
||||
} catch (RuntimeException e) {
|
||||
if (readOnly) lock.readLock().unlock();
|
||||
else lock.writeLock().unlock();
|
||||
throw e;
|
||||
@@ -321,7 +331,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, c, maxLength);
|
||||
List<byte[]> messages = new ArrayList<>(ids.size());
|
||||
List<byte[]> messages = new ArrayList<byte[]>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getRawMessage(txn, m));
|
||||
db.updateExpiryTime(txn, c, m, maxLatency);
|
||||
@@ -371,7 +381,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> ids = db.getRequestedMessagesToSend(txn, c,
|
||||
maxLength);
|
||||
List<byte[]> messages = new ArrayList<>(ids.size());
|
||||
List<byte[]> messages = new ArrayList<byte[]>(ids.size());
|
||||
for (MessageId m : ids) {
|
||||
messages.add(db.getRawMessage(txn, m));
|
||||
db.updateExpiryTime(txn, c, m, maxLatency);
|
||||
@@ -581,13 +591,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessageDependents(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings getSettings(Transaction transaction, String namespace)
|
||||
throws DbException {
|
||||
@@ -658,7 +661,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
Collection<MessageId> acked = new ArrayList<>();
|
||||
Collection<MessageId> acked = new ArrayList<MessageId>();
|
||||
for (MessageId m : a.getMessageIds()) {
|
||||
if (db.containsVisibleMessage(txn, c, m)) {
|
||||
db.raiseSeenFlag(txn, c, m);
|
||||
@@ -767,16 +770,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
transaction.attach(new LocalAuthorRemovedEvent(a));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeMessage(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.removeMessage(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransport(Transaction transaction, TransportId t)
|
||||
throws DbException {
|
||||
@@ -893,7 +886,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
Map<ContactId, TransportKeys> filtered = new HashMap<>();
|
||||
Map<ContactId, TransportKeys> filtered =
|
||||
new HashMap<ContactId, TransportKeys>();
|
||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
TransportKeys k = e.getValue();
|
||||
|
||||
@@ -23,4 +23,10 @@ interface DatabaseConstants {
|
||||
*/
|
||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the minimum supported database
|
||||
* schema version is stored.
|
||||
*/
|
||||
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class DatabaseExecutorModule {
|
||||
|
||||
public DatabaseExecutorModule() {
|
||||
// Use an unbounded queue
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
|
||||
@@ -26,7 +26,7 @@ public class DatabaseModule {
|
||||
@Singleton
|
||||
DatabaseComponent provideDatabaseComponent(Database<Connection> db,
|
||||
EventBus eventBus, ShutdownManager shutdown) {
|
||||
return new DatabaseComponentImpl<>(db, Connection.class, eventBus,
|
||||
shutdown);
|
||||
return new DatabaseComponentImpl<Connection>(db, Connection.class,
|
||||
eventBus, shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
@@ -14,7 +13,6 @@ import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
@@ -42,11 +40,10 @@ class H2Database extends JdbcDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(@Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
public boolean open() throws DbException {
|
||||
boolean reopen = config.databaseExists();
|
||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
||||
super.open("org.h2.Driver", reopen, listener);
|
||||
super.open("org.h2.Driver", reopen);
|
||||
return reopen;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,9 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbClosedException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -50,7 +47,6 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
@@ -61,6 +57,7 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MIN_SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
|
||||
@@ -71,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
@NotNullByDefault
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 31;
|
||||
private static final int SCHEMA_VERSION = 30;
|
||||
private static final int MIN_SCHEMA_VERSION = 30;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -151,16 +148,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private static final String CREATE_MESSAGE_METADATA =
|
||||
"CREATE TABLE messageMetadata"
|
||||
+ " (messageId HASH NOT NULL,"
|
||||
+ " groupId HASH NOT NULL," // Denormalised
|
||||
+ " state INT NOT NULL," // Denormalised
|
||||
+ " key VARCHAR NOT NULL,"
|
||||
+ " value BINARY NOT NULL,"
|
||||
+ " PRIMARY KEY (messageId, key),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
||||
@@ -240,18 +232,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String INDEX_CONTACTS_BY_AUTHOR_ID =
|
||||
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
||||
+ " ON contacts (authorId)";
|
||||
|
||||
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
||||
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
||||
+ " ON groups (clientId)";
|
||||
|
||||
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||
+ " ON messageMetadata (groupId, state)";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JdbcDatabase.class.getName());
|
||||
|
||||
@@ -259,8 +239,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private final String hashType, binaryType, counterType, secretType;
|
||||
private final Clock clock;
|
||||
|
||||
// Locking: connectionsLock
|
||||
private final LinkedList<Connection> connections = new LinkedList<>();
|
||||
private final LinkedList<Connection> connections =
|
||||
new LinkedList<Connection>(); // Locking: connectionsLock
|
||||
|
||||
private int openConnections = 0; // Locking: connectionsLock
|
||||
private boolean closed = false; // Locking: connectionsLock
|
||||
@@ -280,24 +260,22 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
protected void open(String driverClass, boolean reopen,
|
||||
@Nullable MigrationListener listener) throws DbException {
|
||||
protected void open(String driverClass, boolean reopen) throws DbException {
|
||||
// Load the JDBC driver
|
||||
try {
|
||||
Class.forName(driverClass);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new DbException(e);
|
||||
}
|
||||
// Open the database and create the tables and indexes if necessary
|
||||
// Open the database and create the tables if necessary
|
||||
Connection txn = startTransaction();
|
||||
try {
|
||||
if (reopen) {
|
||||
checkSchemaVersion(txn, listener);
|
||||
if (!checkSchemaVersion(txn)) throw new DbException();
|
||||
} else {
|
||||
createTables(txn);
|
||||
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
|
||||
storeSchemaVersion(txn);
|
||||
}
|
||||
createIndexes(txn);
|
||||
commitTransaction(txn);
|
||||
} catch (DbException e) {
|
||||
abortTransaction(txn);
|
||||
@@ -305,51 +283,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the schema version stored in the database with the schema
|
||||
* version used by the current code and applies any suitable migrations to
|
||||
* the data if necessary.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
private void checkSchemaVersion(Connection txn,
|
||||
@Nullable MigrationListener listener) throws DbException {
|
||||
private boolean checkSchemaVersion(Connection txn) throws DbException {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
if (dataSchemaVersion == -1) throw new DbException();
|
||||
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return;
|
||||
if (CODE_SCHEMA_VERSION < dataSchemaVersion)
|
||||
throw new DataTooNewException();
|
||||
// Apply any suitable migrations in order
|
||||
for (Migration<Connection> m : getMigrations()) {
|
||||
int start = m.getStartVersion(), end = m.getEndVersion();
|
||||
if (start == dataSchemaVersion) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Migrating from schema " + start + " to " + end);
|
||||
if (listener != null) listener.onMigrationRun();
|
||||
// Apply the migration
|
||||
m.migrate(txn);
|
||||
// Store the new schema version
|
||||
storeSchemaVersion(txn, end);
|
||||
dataSchemaVersion = end;
|
||||
}
|
||||
}
|
||||
if (dataSchemaVersion != CODE_SCHEMA_VERSION)
|
||||
throw new DataTooOldException();
|
||||
int schemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
if (schemaVersion == SCHEMA_VERSION) return true;
|
||||
if (schemaVersion < MIN_SCHEMA_VERSION) return false;
|
||||
int minSchemaVersion = s.getInt(MIN_SCHEMA_VERSION_KEY, -1);
|
||||
return SCHEMA_VERSION >= minSchemaVersion;
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
List<Migration<Connection>> getMigrations() {
|
||||
return Collections.singletonList(new Migration30_31());
|
||||
}
|
||||
|
||||
private void storeSchemaVersion(Connection txn, int version)
|
||||
throws DbException {
|
||||
private void storeSchemaVersion(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||
s.putInt(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
|
||||
s.putInt(MIN_SCHEMA_VERSION_KEY, MIN_SCHEMA_VERSION);
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
@@ -394,20 +340,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private void createIndexes(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String insertTypeNames(String s) {
|
||||
s = s.replaceAll("HASH", hashType);
|
||||
s = s.replaceAll("BINARY", binaryType);
|
||||
@@ -526,8 +458,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
// Create a contact row
|
||||
String sql = "INSERT INTO contacts"
|
||||
+ " (authorId, name, publicKey, localAuthorId,"
|
||||
+ " verified, active)"
|
||||
+ " (authorId, name, publicKey, localAuthorId, verified, active)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, remote.getId().getBytes());
|
||||
@@ -1062,7 +993,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " FROM contacts";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
List<Contact> contacts = new ArrayList<>();
|
||||
List<Contact> contacts = new ArrayList<Contact>();
|
||||
while (rs.next()) {
|
||||
ContactId contactId = new ContactId(rs.getInt(1));
|
||||
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||
@@ -1096,7 +1027,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, local.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<ContactId> ids = new ArrayList<>();
|
||||
List<ContactId> ids = new ArrayList<ContactId>();
|
||||
while (rs.next()) ids.add(new ContactId(rs.getInt(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1121,7 +1052,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, remote.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<Contact> contacts = new ArrayList<>();
|
||||
List<Contact> contacts = new ArrayList<Contact>();
|
||||
while (rs.next()) {
|
||||
ContactId c = new ContactId(rs.getInt(1));
|
||||
String name = rs.getString(2);
|
||||
@@ -1177,7 +1108,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<Group> groups = new ArrayList<>();
|
||||
List<Group> groups = new ArrayList<Group>();
|
||||
while (rs.next()) {
|
||||
GroupId id = new GroupId(rs.getBytes(1));
|
||||
byte[] descriptor = rs.getBytes(2);
|
||||
@@ -1230,7 +1161,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<ContactId> visible = new ArrayList<>();
|
||||
List<ContactId> visible = new ArrayList<ContactId>();
|
||||
while (rs.next()) visible.add(new ContactId(rs.getInt(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1282,7 +1213,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " FROM localAuthors";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
List<LocalAuthor> authors = new ArrayList<>();
|
||||
List<LocalAuthor> authors = new ArrayList<LocalAuthor>();
|
||||
while (rs.next()) {
|
||||
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
||||
String name = rs.getString(2);
|
||||
@@ -1312,7 +1243,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1335,7 +1266,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1357,17 +1288,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
// Retrieve the message IDs for each query term and intersect
|
||||
Set<MessageId> intersection = null;
|
||||
String sql = "SELECT messageId FROM messageMetadata"
|
||||
+ " WHERE groupId = ? AND state = ?"
|
||||
String sql = "SELECT m.messageId"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN messageMetadata AS md"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE state = ? AND groupId = ?"
|
||||
+ " AND key = ? AND value = ?";
|
||||
for (Entry<String, byte[]> e : query.entrySet()) {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setString(3, e.getKey());
|
||||
ps.setBytes(4, e.getValue());
|
||||
rs = ps.executeQuery();
|
||||
Set<MessageId> ids = new HashSet<>();
|
||||
Set<MessageId> ids = new HashSet<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1391,20 +1325,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId, key, value"
|
||||
+ " FROM messageMetadata"
|
||||
+ " WHERE groupId = ? AND state = ?";
|
||||
String sql = "SELECT m.messageId, key, value"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN messageMetadata AS md"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE state = ? AND groupId = ?"
|
||||
+ " ORDER BY m.messageId";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, Metadata> all = new HashMap<>();
|
||||
Map<MessageId, Metadata> all = new HashMap<MessageId, Metadata>();
|
||||
Metadata metadata = null;
|
||||
MessageId lastMessageId = null;
|
||||
while (rs.next()) {
|
||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||
Metadata metadata = all.get(messageId);
|
||||
if (metadata == null) {
|
||||
if (lastMessageId == null || !messageId.equals(lastMessageId)) {
|
||||
metadata = new Metadata();
|
||||
all.put(messageId, metadata);
|
||||
lastMessageId = messageId;
|
||||
}
|
||||
metadata.put(rs.getString(2), rs.getBytes(3));
|
||||
}
|
||||
@@ -1425,7 +1364,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
Collection<MessageId> matches = getMessageIds(txn, g, query);
|
||||
if (matches.isEmpty()) return Collections.emptyMap();
|
||||
// Retrieve the metadata for each match
|
||||
Map<MessageId, Metadata> all = new HashMap<>(matches.size());
|
||||
Map<MessageId, Metadata> all = new HashMap<MessageId, Metadata>(
|
||||
matches.size());
|
||||
for (MessageId m : matches) all.put(m, getMessageMetadata(txn, m));
|
||||
return all;
|
||||
}
|
||||
@@ -1459,8 +1399,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT key, value FROM messageMetadata"
|
||||
+ " WHERE state = ? AND messageId = ?";
|
||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE m.state = ? AND md.messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
@@ -1483,9 +1425,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT key, value FROM messageMetadata"
|
||||
+ " WHERE (state = ? OR state = ?)"
|
||||
+ " AND messageId = ?";
|
||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE (m.state = ? OR m.state = ?)"
|
||||
+ " AND md.messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setInt(2, PENDING.getValue());
|
||||
@@ -1519,7 +1463,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageStatus> statuses = new ArrayList<>();
|
||||
List<MessageStatus> statuses = new ArrayList<MessageStatus>();
|
||||
while (rs.next()) {
|
||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||
boolean sent = rs.getBoolean(2);
|
||||
@@ -1578,7 +1522,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, State> dependencies = new HashMap<>();
|
||||
Map<MessageId, State> dependencies = new HashMap<MessageId, State>();
|
||||
while (rs.next()) {
|
||||
MessageId dependency = new MessageId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
@@ -1616,7 +1560,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, State> dependents = new HashMap<>();
|
||||
Map<MessageId, State> dependents = new HashMap<MessageId, State>();
|
||||
while (rs.next()) {
|
||||
MessageId dependent = new MessageId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
@@ -1668,7 +1612,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1704,7 +1648,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setLong(3, now);
|
||||
ps.setInt(4, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1729,7 +1673,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, maxMessages);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1764,7 +1708,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
int total = 0;
|
||||
while (rs.next()) {
|
||||
int length = rs.getInt(1);
|
||||
@@ -1806,7 +1750,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setString(2, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1836,7 +1780,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -1848,41 +1792,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT expiry FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
+ " AND seen = FALSE"
|
||||
+ " ORDER BY expiry LIMIT 1";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
long nextSendTime = Long.MAX_VALUE;
|
||||
if (rs.next()) {
|
||||
nextSendTime = rs.getLong(1);
|
||||
if (rs.next()) throw new AssertionError();
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return nextSendTime;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] getRawMessage(Connection txn, MessageId m)
|
||||
@@ -1930,7 +1839,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setLong(3, now);
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
int total = 0;
|
||||
while (rs.next()) {
|
||||
int length = rs.getInt(1);
|
||||
@@ -1984,7 +1893,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<IncomingKeys> inKeys = new ArrayList<>();
|
||||
List<IncomingKeys> inKeys = new ArrayList<IncomingKeys>();
|
||||
while (rs.next()) {
|
||||
long rotationPeriod = rs.getLong(1);
|
||||
SecretKey tagKey = new SecretKey(rs.getBytes(2));
|
||||
@@ -2004,7 +1913,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
Map<ContactId, TransportKeys> keys = new HashMap<>();
|
||||
Map<ContactId, TransportKeys> keys =
|
||||
new HashMap<ContactId, TransportKeys>();
|
||||
for (int i = 0; rs.next(); i++) {
|
||||
// There should be three times as many incoming keys
|
||||
if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
|
||||
@@ -2094,7 +2004,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != requested.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected) {
|
||||
for (int rows: batchAffected) {
|
||||
if (rows < 0) throw new DbStateException();
|
||||
if (rows > 1) throw new DbStateException();
|
||||
}
|
||||
@@ -2108,92 +2018,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
@Override
|
||||
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||
g.getBytes(), meta, "groupMetadata", "groupId");
|
||||
if (added.isEmpty()) return;
|
||||
// Insert any keys that don't already exist
|
||||
String sql = "INSERT INTO groupMetadata (groupId, key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setBytes(3, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != added.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
mergeMetadata(txn, g.getBytes(), meta, "groupMetadata", "groupId");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeMessageMetadata(Connection txn, MessageId m,
|
||||
Metadata meta) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||
m.getBytes(), meta, "messageMetadata", "messageId");
|
||||
if (added.isEmpty()) return;
|
||||
// Get the group ID and message state for the denormalised columns
|
||||
String sql = "SELECT groupId, state FROM messages"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId g = new GroupId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Insert any keys that don't already exist
|
||||
sql = "INSERT INTO messageMetadata"
|
||||
+ " (messageId, groupId, state, key, value)"
|
||||
+ " VALUES (?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setInt(3, state.getValue());
|
||||
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||
ps.setString(4, e.getKey());
|
||||
ps.setBytes(5, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != added.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
public void mergeMessageMetadata(Connection txn, MessageId m, Metadata meta)
|
||||
throws DbException {
|
||||
mergeMetadata(txn, m.getBytes(), meta, "messageMetadata", "messageId");
|
||||
}
|
||||
|
||||
// Removes or updates any existing entries, returns any entries that
|
||||
// need to be added
|
||||
private Map<String, byte[]> removeOrUpdateMetadata(Connection txn,
|
||||
byte[] id, Metadata meta, String tableName, String columnName)
|
||||
throws DbException {
|
||||
private void mergeMetadata(Connection txn, byte[] id, Metadata meta,
|
||||
String tableName, String columnName) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Determine which keys are being removed
|
||||
List<String> removed = new ArrayList<>();
|
||||
Map<String, byte[]> notRemoved = new HashMap<>();
|
||||
List<String> removed = new ArrayList<String>();
|
||||
Map<String, byte[]> retained = new HashMap<String, byte[]>();
|
||||
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||
if (e.getValue() == REMOVE) removed.add(e.getKey());
|
||||
else notRemoved.put(e.getKey(), e.getValue());
|
||||
else retained.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// Delete any keys that are being removed
|
||||
if (!removed.isEmpty()) {
|
||||
@@ -2214,33 +2057,45 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
ps.close();
|
||||
}
|
||||
if (notRemoved.isEmpty()) return Collections.emptyMap();
|
||||
if (retained.isEmpty()) return;
|
||||
// Update any keys that already exist
|
||||
String sql = "UPDATE " + tableName + " SET value = ?"
|
||||
+ " WHERE " + columnName + " = ? AND key = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(2, id);
|
||||
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
||||
ps.setBytes(1, e.getValue());
|
||||
ps.setString(3, e.getKey());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != notRemoved.size())
|
||||
if (batchAffected.length != retained.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected) {
|
||||
if (rows < 0) throw new DbStateException();
|
||||
if (rows > 1) throw new DbStateException();
|
||||
}
|
||||
ps.close();
|
||||
// Are there any keys that don't already exist?
|
||||
Map<String, byte[]> added = new HashMap<>();
|
||||
int updateIndex = 0;
|
||||
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||
if (batchAffected[updateIndex++] == 0)
|
||||
added.put(e.getKey(), e.getValue());
|
||||
// Insert any keys that don't already exist
|
||||
sql = "INSERT INTO " + tableName
|
||||
+ " (" + columnName + ", key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, id);
|
||||
int updateIndex = 0, inserted = 0;
|
||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
||||
if (batchAffected[updateIndex] == 0) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setBytes(3, e.getValue());
|
||||
ps.addBatch();
|
||||
inserted++;
|
||||
}
|
||||
updateIndex++;
|
||||
}
|
||||
return added;
|
||||
batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != inserted) throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2593,8 +2448,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageShared(Connection txn, MessageId m)
|
||||
throws DbException {
|
||||
public void setMessageShared(Connection txn, MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE messages SET shared = TRUE"
|
||||
@@ -2622,14 +2476,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in messageMetadata
|
||||
sql = "UPDATE messageMetadata SET state = ? WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
interface Migration<T> {
|
||||
|
||||
/**
|
||||
* Returns the schema version from which this migration starts.
|
||||
*/
|
||||
int getStartVersion();
|
||||
|
||||
/**
|
||||
* Returns the schema version at which this migration ends.
|
||||
*/
|
||||
int getEndVersion();
|
||||
|
||||
void migrate(T txn) throws DbException;
|
||||
}
|
||||
@@ -1,75 +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 javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
class Migration30_31 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Migration30_31.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 31;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
// Add groupId column
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD COLUMN groupId BINARY(32) AFTER messageId");
|
||||
// Populate groupId column
|
||||
s.execute("UPDATE messageMetadata AS mm SET groupId ="
|
||||
+ " (SELECT groupId FROM messages AS m"
|
||||
+ " WHERE mm.messageId = m.messageId)");
|
||||
// Add not null constraint now column has been populated
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ALTER COLUMN groupId"
|
||||
+ " SET NOT NULL");
|
||||
// Add foreign key constraint
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD CONSTRAINT groupIdForeignKey"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE");
|
||||
// Add state column
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD COLUMN state INT AFTER groupId");
|
||||
// Populate state column
|
||||
s.execute("UPDATE messageMetadata AS mm SET state ="
|
||||
+ " (SELECT state FROM messages AS m"
|
||||
+ " WHERE mm.messageId = m.messageId)");
|
||||
// Add not null constraint now column has been populated
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ALTER COLUMN state"
|
||||
+ " SET NOT NULL");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Statement s) {
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
} catch (SQLException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
class EventBusImpl implements EventBus {
|
||||
|
||||
private final Collection<EventListener> listeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
new CopyOnWriteArrayList<EventListener>();
|
||||
|
||||
@Override
|
||||
public void addListener(EventListener l) {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
interface ConnectionChooser {
|
||||
|
||||
/**
|
||||
* Submits a connection task to the chooser.
|
||||
*/
|
||||
void submit(Callable<KeyAgreementConnection> task);
|
||||
|
||||
/**
|
||||
* Returns a connection returned by any of the tasks submitted to the
|
||||
* chooser, waiting up to the given amount of time for a connection if
|
||||
* necessary. Returns null if the time elapses without a connection
|
||||
* becoming available.
|
||||
*
|
||||
* @param timeout the timeout in milliseconds
|
||||
* @throws InterruptedException if the thread is interrupted while waiting
|
||||
* for a connection to become available
|
||||
*/
|
||||
@Nullable
|
||||
KeyAgreementConnection poll(long timeout) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Stops the chooser. Any connections already returned to the chooser are
|
||||
* closed unless they have been removed from the chooser by calling
|
||||
* {@link #poll(long)}. Any connections subsequently returned to the
|
||||
* chooser will also be closed.
|
||||
*/
|
||||
void stop();
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@NotNullByDefault
|
||||
@ThreadSafe
|
||||
class ConnectionChooserImpl implements ConnectionChooser {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectionChooserImpl.class.getName());
|
||||
|
||||
private final Clock clock;
|
||||
private final Executor ioExecutor;
|
||||
private final Object lock = new Object();
|
||||
|
||||
// The following are locking: lock
|
||||
private boolean stopped = false;
|
||||
private final Queue<KeyAgreementConnection> results = new LinkedList<>();
|
||||
|
||||
@Inject
|
||||
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
|
||||
this.clock = clock;
|
||||
this.ioExecutor = ioExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(Callable<KeyAgreementConnection> task) {
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
KeyAgreementConnection c = task.call();
|
||||
if (c != null) addResult(c);
|
||||
} catch (Exception e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection poll(long timeout)
|
||||
throws InterruptedException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + timeout;
|
||||
synchronized (lock) {
|
||||
while (!stopped && results.isEmpty() && now < end) {
|
||||
lock.wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
return results.poll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
List<KeyAgreementConnection> unused;
|
||||
synchronized (lock) {
|
||||
unused = new ArrayList<>(results);
|
||||
results.clear();
|
||||
stopped = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing " + unused.size() + " unused connections");
|
||||
for (KeyAgreementConnection c : unused) tryToClose(c.getConnection());
|
||||
}
|
||||
|
||||
private void addResult(KeyAgreementConnection c) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Got connection for " + c.getTransportId());
|
||||
boolean close = false;
|
||||
synchronized (lock) {
|
||||
if (stopped) {
|
||||
close = true;
|
||||
} else {
|
||||
results.add(c);
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
if (close) {
|
||||
LOG.info("Already stopped");
|
||||
tryToClose(c.getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
try {
|
||||
conn.getReader().dispose(false, true);
|
||||
conn.getWriter().dispose(false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,19 +13,23 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
||||
@@ -41,42 +45,46 @@ class KeyAgreementConnector {
|
||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||
|
||||
private final Callbacks callbacks;
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final PluginManager pluginManager;
|
||||
private final ConnectionChooser connectionChooser;
|
||||
private final CompletionService<KeyAgreementConnection> connect;
|
||||
|
||||
private final List<KeyAgreementListener> listeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final CountDownLatch aliceLatch = new CountDownLatch(1);
|
||||
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
|
||||
new ArrayList<KeyAgreementListener>();
|
||||
private final List<Future<KeyAgreementConnection>> pending =
|
||||
new ArrayList<Future<KeyAgreementConnection>>();
|
||||
|
||||
private volatile boolean alice = false, stopped = false;
|
||||
private volatile boolean connecting = false;
|
||||
private volatile boolean alice = false;
|
||||
|
||||
KeyAgreementConnector(Callbacks callbacks,
|
||||
KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
||||
CryptoComponent crypto, PluginManager pluginManager,
|
||||
ConnectionChooser connectionChooser) {
|
||||
Executor ioExecutor) {
|
||||
this.callbacks = callbacks;
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionChooser = connectionChooser;
|
||||
connect = new ExecutorCompletionService<KeyAgreementConnection>(
|
||||
ioExecutor);
|
||||
}
|
||||
|
||||
Payload listen(KeyPair localKeyPair) {
|
||||
public Payload listen(KeyPair localKeyPair) {
|
||||
LOG.info("Starting BQP listeners");
|
||||
// Derive commitment
|
||||
byte[] commitment = crypto.deriveKeyCommitment(
|
||||
localKeyPair.getPublic().getEncoded());
|
||||
// Start all listeners and collect their descriptors
|
||||
List<TransportDescriptor> descriptors = new ArrayList<>();
|
||||
List<TransportDescriptor> descriptors =
|
||||
new ArrayList<TransportDescriptor>();
|
||||
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
|
||||
KeyAgreementListener l =
|
||||
plugin.createKeyAgreementListener(commitment);
|
||||
if (l != null) {
|
||||
TransportId id = plugin.getId();
|
||||
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
|
||||
pending.add(connect.submit(new ReadableTask(l.listen())));
|
||||
listeners.add(l);
|
||||
connectionChooser.submit(new ReadableTask(l::accept));
|
||||
}
|
||||
}
|
||||
return new Payload(commitment, descriptors);
|
||||
@@ -84,92 +92,128 @@ class KeyAgreementConnector {
|
||||
|
||||
void stopListening() {
|
||||
LOG.info("Stopping BQP listeners");
|
||||
stopped = true;
|
||||
aliceLatch.countDown();
|
||||
for (KeyAgreementListener l : listeners) l.close();
|
||||
connectionChooser.stop();
|
||||
for (KeyAgreementListener l : listeners) {
|
||||
l.close();
|
||||
}
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
|
||||
// Let the ReadableTasks know if we are Alice
|
||||
public KeyAgreementTransport connect(Payload remotePayload,
|
||||
boolean alice) {
|
||||
// Let the listeners know if we are Alice
|
||||
this.connecting = true;
|
||||
this.alice = alice;
|
||||
aliceLatch.countDown();
|
||||
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||
|
||||
// Start connecting over supported transports
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Starting outgoing BQP connections as "
|
||||
+ (alice ? "Alice" : "Bob"));
|
||||
}
|
||||
LOG.info("Starting outgoing BQP connections");
|
||||
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
||||
Plugin p = pluginManager.getPlugin(d.getId());
|
||||
if (p instanceof DuplexPlugin) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting via " + d.getId());
|
||||
DuplexPlugin plugin = (DuplexPlugin) p;
|
||||
byte[] commitment = remotePayload.getCommitment();
|
||||
BdfList descriptor = d.getDescriptor();
|
||||
connectionChooser.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, commitment, descriptor)));
|
||||
pending.add(connect.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, remotePayload.getCommitment(),
|
||||
d.getDescriptor(), end))));
|
||||
}
|
||||
}
|
||||
|
||||
// Get chosen connection
|
||||
KeyAgreementConnection chosen = null;
|
||||
try {
|
||||
KeyAgreementConnection chosen =
|
||||
connectionChooser.poll(CONNECTION_TIMEOUT);
|
||||
if (chosen == null) return null;
|
||||
long now = clock.currentTimeMillis();
|
||||
Future<KeyAgreementConnection> f =
|
||||
connect.poll(end - now, MILLISECONDS);
|
||||
if (f == null)
|
||||
return null; // No task completed within the timeout.
|
||||
chosen = f.get();
|
||||
return new KeyAgreementTransport(chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
stopListening();
|
||||
// Close all other connections
|
||||
closePending(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitingForAlice() {
|
||||
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
|
||||
private void closePending(@Nullable KeyAgreementConnection chosen) {
|
||||
for (Future<KeyAgreementConnection> f : pending) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else if (!f.isCancelled()) {
|
||||
KeyAgreementConnection c = f.get();
|
||||
if (c != null && c != chosen)
|
||||
tryToClose(c.getConnection(), false);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing connection, exception: " + exception);
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final byte[] commitment;
|
||||
private final BdfList descriptor;
|
||||
private final long end;
|
||||
private final DuplexPlugin plugin;
|
||||
|
||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
||||
BdfList descriptor) {
|
||||
BdfList descriptor, long end) {
|
||||
this.plugin = plugin;
|
||||
this.commitment = commitment;
|
||||
this.descriptor = descriptor;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
// Repeat attempts until we connect, get stopped, or get interrupted
|
||||
while (!stopped) {
|
||||
// Repeat attempts until we connect, get interrupted, or time out
|
||||
while (true) {
|
||||
long now = clock.currentTimeMillis();
|
||||
if (now > end) throw new IOException();
|
||||
DuplexTransportConnection conn =
|
||||
plugin.createKeyAgreementConnection(commitment,
|
||||
descriptor);
|
||||
descriptor, end - now);
|
||||
if (conn != null) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(plugin.getId() + ": Outgoing connection");
|
||||
LOG.info(plugin.getId().getString() +
|
||||
": Outgoing connection");
|
||||
return new KeyAgreementConnection(conn, plugin.getId());
|
||||
}
|
||||
// Wait 2s before retry (to circumvent transient failures)
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReadableTask implements Callable<KeyAgreementConnection> {
|
||||
private class ReadableTask
|
||||
implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final Callable<KeyAgreementConnection> connectionTask;
|
||||
|
||||
@@ -177,23 +221,24 @@ class KeyAgreementConnector {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
KeyAgreementConnection c = connectionTask.call();
|
||||
if (c == null) return null;
|
||||
aliceLatch.await();
|
||||
if (alice || stopped) return c;
|
||||
// Bob waits here for Alice to scan his QR code, determine her
|
||||
// role, and send her key
|
||||
InputStream in = c.getConnection().getReader().getInputStream();
|
||||
while (!stopped && in.available() == 0) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId() + ": Waiting for data");
|
||||
waitingForAlice();
|
||||
Thread.sleep(500);
|
||||
boolean waitingSent = false;
|
||||
while (!alice && in.available() == 0) {
|
||||
if (!waitingSent && connecting && !alice) {
|
||||
// Bob waits here until Alice obtains his payload.
|
||||
callbacks.connectionWaiting();
|
||||
waitingSent = true;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(c.getTransportId().getString() +
|
||||
": Waiting for connection");
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (!stopped && LOG.isLoggable(INFO))
|
||||
if (!alice && LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId().getString() + ": Data available");
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -13,9 +22,13 @@ import dagger.Provides;
|
||||
public class KeyAgreementModule {
|
||||
|
||||
@Provides
|
||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(
|
||||
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) {
|
||||
return keyAgreementTaskFactory;
|
||||
@Singleton
|
||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
|
||||
CryptoComponent crypto, EventBus eventBus,
|
||||
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager) {
|
||||
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
|
||||
ioExecutor, payloadEncoder, pluginManager);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -27,10 +40,4 @@ public class KeyAgreementModule {
|
||||
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
||||
return new PayloadParserImpl(bdfReaderFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ConnectionChooser provideConnectionChooser(
|
||||
ConnectionChooserImpl connectionChooser) {
|
||||
return connectionChooser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +89,7 @@ class KeyAgreementProtocol {
|
||||
byte[] theirPublicKey;
|
||||
if (alice) {
|
||||
sendKey();
|
||||
// Alice waits here for Bob to scan her QR code, determine his
|
||||
// role, receive her key and respond with his key
|
||||
// Alice waits here until Bob obtains her payload.
|
||||
callbacks.connectionWaiting();
|
||||
theirPublicKey = receiveKey();
|
||||
} else {
|
||||
|
||||
@@ -5,37 +5,42 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
||||
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final EventBus eventBus;
|
||||
private final Executor ioExecutor;
|
||||
private final PayloadEncoder payloadEncoder;
|
||||
private final PluginManager pluginManager;
|
||||
private final Provider<ConnectionChooser> connectionChooserProvider;
|
||||
|
||||
@Inject
|
||||
KeyAgreementTaskFactoryImpl(CryptoComponent crypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
Provider<ConnectionChooser> connectionChooserProvider) {
|
||||
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, @IoExecutor Executor ioExecutor,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionChooserProvider = connectionChooserProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementTask createTask() {
|
||||
return new KeyAgreementTaskImpl(crypto, eventBus, payloadEncoder,
|
||||
pluginManager, connectionChooserProvider.get());
|
||||
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
|
||||
pluginManager, ioExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,16 +17,19 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
|
||||
class KeyAgreementTaskImpl extends Thread implements
|
||||
KeyAgreementTask, KeyAgreementConnector.Callbacks,
|
||||
KeyAgreementProtocol.Callbacks {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||
@@ -40,15 +43,15 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
private Payload localPayload;
|
||||
private Payload remotePayload;
|
||||
|
||||
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
ConnectionChooser connectionChooser) {
|
||||
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager, Executor ioExecutor) {
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
localKeyPair = crypto.generateAgreementKeyPair();
|
||||
connector = new KeyAgreementConnector(this, crypto, pluginManager,
|
||||
connectionChooser);
|
||||
connector = new KeyAgreementConnector(this, clock, crypto,
|
||||
pluginManager, ioExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,8 +65,10 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
@Override
|
||||
public synchronized void stopListening() {
|
||||
if (localPayload != null) {
|
||||
if (remotePayload == null) connector.stopListening();
|
||||
else interrupt();
|
||||
if (remotePayload == null)
|
||||
connector.stopListening();
|
||||
else
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ class PayloadParserImpl implements PayloadParser {
|
||||
byte[] commitment = payload.getRaw(1);
|
||||
if (commitment.length != COMMIT_LENGTH) throw new FormatException();
|
||||
// Remaining elements: transport descriptors
|
||||
List<TransportDescriptor> recognised = new ArrayList<>();
|
||||
List<TransportDescriptor> recognised =
|
||||
new ArrayList<TransportDescriptor>();
|
||||
for (int i = 2; i < payload.size(); i++) {
|
||||
BdfList descriptor = payload.getList(i);
|
||||
long transportId = descriptor.getLong(0);
|
||||
|
||||
@@ -2,11 +2,8 @@ package org.briarproject.bramble.lifecycle;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
@@ -15,7 +12,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
|
||||
@@ -32,21 +29,14 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
class LifecycleManagerImpl implements LifecycleManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LifecycleManagerImpl.class.getName());
|
||||
@@ -64,8 +54,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
|
||||
private volatile LifecycleState state = STARTING;
|
||||
|
||||
@Inject
|
||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||
CryptoComponent crypto, AuthorFactory authorFactory,
|
||||
@@ -75,9 +63,9 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
this.crypto = crypto;
|
||||
this.authorFactory = authorFactory;
|
||||
this.identityManager = identityManager;
|
||||
services = new CopyOnWriteArrayList<>();
|
||||
clients = new CopyOnWriteArrayList<>();
|
||||
executors = new CopyOnWriteArrayList<>();
|
||||
services = new CopyOnWriteArrayList<Service>();
|
||||
clients = new CopyOnWriteArrayList<Client>();
|
||||
executors = new CopyOnWriteArrayList<ExecutorService>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,7 +88,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
executors.add(e);
|
||||
}
|
||||
|
||||
private LocalAuthor createLocalAuthor(String nickname) {
|
||||
private LocalAuthor createLocalAuthor(final String nickname) {
|
||||
long now = System.currentTimeMillis();
|
||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||
@@ -131,7 +119,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
LOG.info("Starting services");
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
boolean reopened = db.open(this);
|
||||
boolean reopened = db.open();
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
if (reopened)
|
||||
@@ -143,10 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
registerLocalAuthor(createLocalAuthor(nickname));
|
||||
}
|
||||
|
||||
state = STARTING_SERVICES;
|
||||
dbLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
for (Client c : clients) {
|
||||
@@ -172,17 +157,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
+ " took " + duration + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
state = RUNNING;
|
||||
startupLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||
return SUCCESS;
|
||||
} catch (DataTooOldException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DATA_TOO_OLD_ERROR;
|
||||
} catch (DataTooNewException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DATA_TOO_NEW_ERROR;
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DB_ERROR;
|
||||
@@ -194,12 +170,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMigrationRun() {
|
||||
state = MIGRATING_DATABASE;
|
||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServices() {
|
||||
try {
|
||||
@@ -210,8 +180,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
}
|
||||
try {
|
||||
LOG.info("Stopping services");
|
||||
state = STOPPING;
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
eventBus.broadcast(new ShutdownEvent());
|
||||
for (Service s : services) {
|
||||
long start = System.currentTimeMillis();
|
||||
s.stopService();
|
||||
@@ -234,7 +203,9 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing database took " + duration + " ms");
|
||||
shutdownLatch.countDown();
|
||||
} catch (DbException | ServiceException e) {
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (ServiceException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
startStopSemaphore.release();
|
||||
@@ -256,8 +227,4 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
shutdownLatch.await();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class LifecycleModule {
|
||||
|
||||
public LifecycleModule() {
|
||||
// The thread pool is unbounded, so use direct handoff
|
||||
BlockingQueue<Runnable> queue = new SynchronousQueue<>();
|
||||
BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ThreadPoolExecutor.DiscardPolicy();
|
||||
|
||||
@@ -21,7 +21,7 @@ class ShutdownManagerImpl implements ShutdownManager {
|
||||
private int nextHandle = 0;
|
||||
|
||||
ShutdownManagerImpl() {
|
||||
hooks = new HashMap<>();
|
||||
hooks = new HashMap<Integer, Thread>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -134,7 +134,11 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
try {
|
||||
byte[] tag = readTag(reader);
|
||||
ctx = keyManager.getStreamContext(transportId, tag);
|
||||
} catch (IOException | DbException e) {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
return;
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
return;
|
||||
@@ -245,7 +249,11 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
try {
|
||||
byte[] tag = readTag(reader);
|
||||
ctx = keyManager.getStreamContext(transportId, tag);
|
||||
} catch (IOException | DbException e) {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
return;
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
return;
|
||||
@@ -258,7 +266,12 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
contactId = ctx.getContactId();
|
||||
connectionRegistry.registerConnection(contactId, transportId, true);
|
||||
// Start the outgoing session on another thread
|
||||
ioExecutor.execute(this::runOutgoingSession);
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runOutgoingSession();
|
||||
}
|
||||
});
|
||||
try {
|
||||
// Create and run the incoming session
|
||||
incomingSession = createIncomingSession(ctx, reader);
|
||||
@@ -355,7 +368,12 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
return;
|
||||
}
|
||||
// Start the incoming session on another thread
|
||||
ioExecutor.execute(this::runIncomingSession);
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runIncomingSession();
|
||||
}
|
||||
});
|
||||
try {
|
||||
// Create and run the outgoing session
|
||||
outgoingSession = createDuplexOutgoingSession(ctx, writer);
|
||||
@@ -373,7 +391,11 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
try {
|
||||
byte[] tag = readTag(reader);
|
||||
ctx = keyManager.getStreamContext(transportId, tag);
|
||||
} catch (IOException | DbException e) {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
return;
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
return;
|
||||
|
||||
@@ -42,8 +42,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
@Inject
|
||||
ConnectionRegistryImpl(EventBus eventBus) {
|
||||
this.eventBus = eventBus;
|
||||
connections = new HashMap<>();
|
||||
contactCounts = new HashMap<>();
|
||||
connections = new HashMap<TransportId, Map<ContactId, Integer>>();
|
||||
contactCounts = new HashMap<ContactId, Integer>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +58,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
try {
|
||||
Map<ContactId, Integer> m = connections.get(t);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
m = new HashMap<ContactId, Integer>();
|
||||
connections.put(t, m);
|
||||
}
|
||||
Integer count = m.get(c);
|
||||
@@ -124,7 +124,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
try {
|
||||
Map<ContactId, Integer> m = connections.get(t);
|
||||
if (m == null) return Collections.emptyList();
|
||||
List<ContactId> ids = new ArrayList<>(m.keySet());
|
||||
List<ContactId> ids = new ArrayList<ContactId>(m.keySet());
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ids.size() + " contacts connected");
|
||||
return ids;
|
||||
|
||||
@@ -82,10 +82,10 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
this.settingsManager = settingsManager;
|
||||
this.transportPropertyManager = transportPropertyManager;
|
||||
this.uiCallback = uiCallback;
|
||||
plugins = new ConcurrentHashMap<>();
|
||||
simplexPlugins = new CopyOnWriteArrayList<>();
|
||||
duplexPlugins = new CopyOnWriteArrayList<>();
|
||||
startLatches = new ConcurrentHashMap<>();
|
||||
plugins = new ConcurrentHashMap<TransportId, Plugin>();
|
||||
simplexPlugins = new CopyOnWriteArrayList<SimplexPlugin>();
|
||||
duplexPlugins = new CopyOnWriteArrayList<DuplexPlugin>();
|
||||
startLatches = new ConcurrentHashMap<TransportId, CountDownLatch>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,17 +156,17 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPlugin> getSimplexPlugins() {
|
||||
return new ArrayList<>(simplexPlugins);
|
||||
return new ArrayList<SimplexPlugin>(simplexPlugins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPlugin> getDuplexPlugins() {
|
||||
return new ArrayList<>(duplexPlugins);
|
||||
return new ArrayList<DuplexPlugin>(duplexPlugins);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPlugin> getKeyAgreementPlugins() {
|
||||
List<DuplexPlugin> supported = new ArrayList<>();
|
||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||
for (DuplexPlugin d : duplexPlugins)
|
||||
if (d.supportsKeyAgreement()) supported.add(d);
|
||||
return supported;
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -26,7 +25,6 @@ import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -52,7 +50,7 @@ class Poller implements EventListener {
|
||||
private final SecureRandom random;
|
||||
private final Clock clock;
|
||||
private final Lock lock;
|
||||
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
|
||||
private final Map<TransportId, PollTask> tasks; // Locking: lock
|
||||
|
||||
@Inject
|
||||
Poller(@IoExecutor Executor ioExecutor,
|
||||
@@ -68,7 +66,7 @@ class Poller implements EventListener {
|
||||
this.random = random;
|
||||
this.clock = clock;
|
||||
lock = new ReentrantLock();
|
||||
tasks = new HashMap<>();
|
||||
tasks = new HashMap<TransportId, PollTask>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,10 +93,6 @@ class Poller implements EventListener {
|
||||
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
||||
// Poll the newly enabled transport
|
||||
pollNow(t.getTransportId());
|
||||
} else if (e instanceof TransportDisabledEvent) {
|
||||
TransportDisabledEvent t = (TransportDisabledEvent) e;
|
||||
// Cancel polling for the disabled transport
|
||||
cancel(t.getTransportId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,24 +111,30 @@ class Poller implements EventListener {
|
||||
connectToContact(c, (DuplexPlugin) p);
|
||||
}
|
||||
|
||||
private void connectToContact(ContactId c, SimplexPlugin p) {
|
||||
ioExecutor.execute(() -> {
|
||||
TransportId t = p.getId();
|
||||
if (!connectionRegistry.isConnected(c, t)) {
|
||||
TransportConnectionWriter w = p.createWriter(c);
|
||||
if (w != null)
|
||||
connectionManager.manageOutgoingConnection(c, t, w);
|
||||
private void connectToContact(final ContactId c, final SimplexPlugin p) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TransportId t = p.getId();
|
||||
if (!connectionRegistry.isConnected(c, t)) {
|
||||
TransportConnectionWriter w = p.createWriter(c);
|
||||
if (w != null)
|
||||
connectionManager.manageOutgoingConnection(c, t, w);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void connectToContact(ContactId c, DuplexPlugin p) {
|
||||
ioExecutor.execute(() -> {
|
||||
TransportId t = p.getId();
|
||||
if (!connectionRegistry.isConnected(c, t)) {
|
||||
DuplexTransportConnection d = p.createConnection(c);
|
||||
if (d != null)
|
||||
connectionManager.manageOutgoingConnection(c, t, d);
|
||||
private void connectToContact(final ContactId c, final DuplexPlugin p) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TransportId t = p.getId();
|
||||
if (!connectionRegistry.isConnected(c, t)) {
|
||||
DuplexTransportConnection d = p.createConnection(c);
|
||||
if (d != null)
|
||||
connectionManager.manageOutgoingConnection(c, t, d);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -157,49 +157,29 @@ class Poller implements EventListener {
|
||||
TransportId t = p.getId();
|
||||
lock.lock();
|
||||
try {
|
||||
ScheduledPollTask scheduled = tasks.get(t);
|
||||
if (scheduled == null || due < scheduled.task.due) {
|
||||
// If a later task exists, cancel it. If it's already started
|
||||
// it will abort safely when it finds it's been replaced
|
||||
if (scheduled != null) scheduled.future.cancel(false);
|
||||
PollTask task = new PollTask(p, due, randomiseNext);
|
||||
Future future = scheduler.schedule(
|
||||
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
||||
tasks.put(t, new ScheduledPollTask(task, future));
|
||||
PollTask scheduled = tasks.get(t);
|
||||
if (scheduled == null || due < scheduled.due) {
|
||||
final PollTask task = new PollTask(p, due, randomiseNext);
|
||||
tasks.put(t, task);
|
||||
scheduler.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ioExecutor.execute(task);
|
||||
}
|
||||
}, delay, MILLISECONDS);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancel(TransportId t) {
|
||||
lock.lock();
|
||||
try {
|
||||
ScheduledPollTask scheduled = tasks.remove(t);
|
||||
if (scheduled != null) scheduled.future.cancel(false);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void poll(Plugin p) {
|
||||
private void poll(final Plugin p) {
|
||||
TransportId t = p.getId();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
|
||||
p.poll(connectionRegistry.getConnectedContacts(t));
|
||||
}
|
||||
|
||||
private class ScheduledPollTask {
|
||||
|
||||
private final PollTask task;
|
||||
private final Future future;
|
||||
|
||||
private ScheduledPollTask(PollTask task, Future future) {
|
||||
this.task = task;
|
||||
this.future = future;
|
||||
}
|
||||
}
|
||||
|
||||
private class PollTask implements Runnable {
|
||||
|
||||
private final Plugin plugin;
|
||||
@@ -218,9 +198,7 @@ class Poller implements EventListener {
|
||||
lock.lock();
|
||||
try {
|
||||
TransportId t = plugin.getId();
|
||||
ScheduledPollTask scheduled = tasks.get(t);
|
||||
if (scheduled != null && scheduled.task != this)
|
||||
return; // Replaced by another task
|
||||
if (tasks.get(t) != this) return; // Replaced by another task
|
||||
tasks.remove(t);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
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.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
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.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
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 static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
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.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxLatency;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false, contactConnections = false;
|
||||
private volatile String contactConnectionsUuid = null;
|
||||
private volatile SS socket = null;
|
||||
|
||||
abstract void initialiseAdapter() throws IOException;
|
||||
|
||||
abstract boolean isAdapterEnabled();
|
||||
|
||||
abstract void enableAdapter();
|
||||
|
||||
abstract void disableAdapterIfEnabledByUs();
|
||||
|
||||
abstract void setEnabledByUs();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
this.callback = callback;
|
||||
this.maxLatency = maxLatency;
|
||||
}
|
||||
|
||||
void onAdapterEnabled() {
|
||||
LOG.info("Bluetooth enabled");
|
||||
// We may not have been able to get the local address before
|
||||
ioExecutor.execute(this::updateProperties);
|
||||
if (shouldAllowContactConnections()) bind();
|
||||
}
|
||||
|
||||
void onAdapterDisabled() {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
callback.transportDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
// Bluetooth detects dead connections so we don't need keepalives
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
try {
|
||||
initialiseAdapter();
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
updateProperties();
|
||||
running = true;
|
||||
loadSettings();
|
||||
if (shouldAllowContactConnections()) {
|
||||
if (isAdapterEnabled()) bind();
|
||||
else enableAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSettings() {
|
||||
contactConnections =
|
||||
callback.getSettings().getBoolean(PREF_BT_ENABLE, false);
|
||||
}
|
||||
|
||||
private boolean shouldAllowContactConnections() {
|
||||
return contactConnections;
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
// Bind a server socket to accept connections from contacts
|
||||
SS ss;
|
||||
try {
|
||||
ss = openServerSocket(contactConnectionsUuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!isRunning() || !shouldAllowContactConnections()) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateProperties() {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
String uuid = p.get(PROP_UUID);
|
||||
boolean changed = false;
|
||||
if (address == null) {
|
||||
address = getBluetoothAddress();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
if (!StringUtils.isNullOrEmpty(address)) {
|
||||
p.put(PROP_ADDRESS, address);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (uuid == null) {
|
||||
byte[] random = new byte[UUID_BYTES];
|
||||
secureRandom.nextBytes(random);
|
||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||
p.put(PROP_UUID, uuid);
|
||||
changed = true;
|
||||
}
|
||||
contactConnectionsUuid = uuid;
|
||||
if (changed) callback.mergeLocalProperties(p);
|
||||
}
|
||||
|
||||
private void acceptContactConnections() {
|
||||
while (true) {
|
||||
DuplexTransportConnection conn;
|
||||
try {
|
||||
conn = acceptConnection(socket);
|
||||
} catch (IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
return;
|
||||
}
|
||||
backoff.reset();
|
||||
callback.incomingConnectionCreated(conn);
|
||||
if (!running) return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
tryToClose(socket);
|
||||
callback.transportDisabled();
|
||||
disableAdapterIfEnabledByUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running && isAdapterEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@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(ContactId c) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
return connect(address, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (!isRunning()) return null;
|
||||
// There's no point listening if we can't discover our own address
|
||||
String address = getBluetoothAddress();
|
||||
if (address == null) 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) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
descriptor.add(StringUtils.macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
if (!isRunning()) return null;
|
||||
String address;
|
||||
try {
|
||||
address = parseAddress(descriptor);
|
||||
} catch (FormatException e) {
|
||||
LOG.info("Invalid address in key agreement descriptor");
|
||||
return null;
|
||||
}
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
return connect(address, uuid);
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
byte[] mac = descriptor.getRaw(1);
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
return StringUtils.macToString(mac);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof EnableBluetoothEvent) {
|
||||
ioExecutor.execute(this::enableAdapter);
|
||||
} else if (e instanceof DisableBluetoothEvent) {
|
||||
ioExecutor.execute(this::disableAdapterIfEnabledByUs);
|
||||
} else if (e instanceof BluetoothEnabledEvent) {
|
||||
setEnabledByUs();
|
||||
} else if (e instanceof SettingsUpdatedEvent) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString()))
|
||||
ioExecutor.execute(this::onSettingsUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSettingsUpdated() {
|
||||
boolean wasAllowed = shouldAllowContactConnections();
|
||||
loadSettings();
|
||||
boolean isAllowed = shouldAllowContactConnections();
|
||||
if (wasAllowed && !isAllowed) {
|
||||
LOG.info("Contact connections disabled");
|
||||
tryToClose(socket);
|
||||
callback.transportDisabled();
|
||||
disableAdapterIfEnabledByUs();
|
||||
} else if (!wasAllowed && isAllowed) {
|
||||
LOG.info("Contact connections enabled");
|
||||
if (isAdapterEnabled()) bind();
|
||||
else enableAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
return new KeyAgreementConnection(conn, ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ abstract class FilePlugin implements SimplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
protected void createReaderFromFile(File f) {
|
||||
protected void createReaderFromFile(final File f) {
|
||||
if (!running) return;
|
||||
ioExecutor.execute(new ReaderCreator(f));
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -43,9 +44,6 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LanTcpPlugin.class.getName());
|
||||
|
||||
private static final LanAddressComparator ADDRESS_COMPARATOR =
|
||||
new LanAddressComparator();
|
||||
|
||||
private static final int MAX_ADDRESSES = 4;
|
||||
private static final String SEPARATOR = ",";
|
||||
|
||||
@@ -65,25 +63,26 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
String oldIpPorts = p.get(PROP_IP_PORTS);
|
||||
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
||||
List<InetSocketAddress> locals = new ArrayList<>();
|
||||
List<InetSocketAddress> locals = new LinkedList<InetSocketAddress>();
|
||||
for (InetAddress local : getLocalIpAddresses()) {
|
||||
if (isAcceptableAddress(local)) {
|
||||
// If this is the old address, try to use the same port
|
||||
for (InetSocketAddress old : olds) {
|
||||
if (old.getAddress().equals(local))
|
||||
locals.add(new InetSocketAddress(local, old.getPort()));
|
||||
if (old.getAddress().equals(local)) {
|
||||
int port = old.getPort();
|
||||
locals.add(0, new InetSocketAddress(local, port));
|
||||
}
|
||||
}
|
||||
locals.add(new InetSocketAddress(local, 0));
|
||||
}
|
||||
}
|
||||
Collections.sort(locals, ADDRESS_COMPARATOR);
|
||||
return locals;
|
||||
}
|
||||
|
||||
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
|
||||
if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList();
|
||||
String[] split = ipPorts.split(SEPARATOR);
|
||||
List<InetSocketAddress> addresses = new ArrayList<>();
|
||||
List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
|
||||
for (String ipPort : split) {
|
||||
InetSocketAddress a = parseSocketAddress(ipPort);
|
||||
if (a != null) addresses.add(a);
|
||||
@@ -96,7 +95,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
String ipPort = getIpPortString(a);
|
||||
// Get the list of recently used addresses
|
||||
String setting = callback.getSettings().get(PREF_LAN_IP_PORTS);
|
||||
List<String> recent = new ArrayList<>();
|
||||
List<String> recent = new ArrayList<String>();
|
||||
if (!StringUtils.isNullOrEmpty(setting))
|
||||
Collections.addAll(recent, setting.split(SEPARATOR));
|
||||
// Is the address already in the list?
|
||||
@@ -112,7 +111,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
recent = recent.subList(0, MAX_ADDRESSES);
|
||||
setting = StringUtils.join(recent, SEPARATOR);
|
||||
// Update the list of addresses shared with contacts
|
||||
List<String> shared = new ArrayList<>(recent);
|
||||
List<String> shared = new ArrayList<String>(recent);
|
||||
Collections.sort(shared);
|
||||
String property = StringUtils.join(shared, SEPARATOR);
|
||||
TransportProperties properties = new TransportProperties();
|
||||
@@ -154,39 +153,17 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
// Package access for testing
|
||||
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
||||
// 10.0.0.0/8
|
||||
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
|
||||
if (localIp[0] == 10) return remoteIp[0] == 10;
|
||||
// 172.16.0.0/12
|
||||
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
|
||||
if (localIp[0] == (byte) 172 && (localIp[1] & 0xF0) == 16)
|
||||
return remoteIp[0] == (byte) 172 && (remoteIp[1] & 0xF0) == 16;
|
||||
// 192.168.0.0/16
|
||||
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
|
||||
if (localIp[0] == (byte) 192 && localIp[1] == (byte) 168)
|
||||
return remoteIp[0] == (byte) 192 && remoteIp[1] == (byte) 168;
|
||||
// Unrecognised prefix - may be compatible
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isPrefix10(byte[] ipv4) {
|
||||
return ipv4[0] == 10;
|
||||
}
|
||||
|
||||
private static boolean isPrefix172(byte[] ipv4) {
|
||||
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
|
||||
}
|
||||
|
||||
private static boolean isPrefix192(byte[] ipv4) {
|
||||
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
|
||||
}
|
||||
|
||||
// Returns the prefix length for an RFC 1918 address, or 0 for any other
|
||||
// address
|
||||
private static int getRfc1918PrefixLength(InetAddress addr) {
|
||||
if (!(addr instanceof Inet4Address)) return 0;
|
||||
if (!addr.isSiteLocalAddress()) return 0;
|
||||
byte[] ipv4 = addr.getAddress();
|
||||
if (isPrefix10(ipv4)) return 8;
|
||||
if (isPrefix172(ipv4)) return 12;
|
||||
if (isPrefix192(ipv4)) return 16;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
@@ -223,7 +200,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
if (!isRunning()) return null;
|
||||
InetSocketAddress remote;
|
||||
try {
|
||||
@@ -282,11 +259,18 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
Socket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
return new KeyAgreementConnection(new TcpTransportConnection(
|
||||
LanTcpPlugin.this, s), ID);
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return new Callable<KeyAgreementConnection>() {
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws IOException {
|
||||
Socket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new TcpTransportConnection(LanTcpPlugin.this, s),
|
||||
ID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -298,19 +282,4 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class LanAddressComparator implements Comparator<InetSocketAddress> {
|
||||
|
||||
@Override
|
||||
public int compare(InetSocketAddress a, InetSocketAddress b) {
|
||||
// Prefer addresses with non-zero ports
|
||||
int aPort = a.getPort(), bPort = b.getPort();
|
||||
if (aPort > 0 && bPort == 0) return -1;
|
||||
if (aPort == 0 && bPort > 0) return 1;
|
||||
// Prefer addresses with longer RFC 1918 prefixes
|
||||
int aPrefix = getRfc1918PrefixLength(a.getAddress());
|
||||
int bPrefix = getRfc1918PrefixLength(b.getAddress());
|
||||
return bPrefix - aPrefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class PortMapperImpl implements PortMapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappingResult map(int port) {
|
||||
public MappingResult map(final int port) {
|
||||
if (!started.getAndSet(true)) start();
|
||||
if (gateway == null) return null;
|
||||
InetAddress internal = gateway.getLocalAddress();
|
||||
@@ -50,7 +50,12 @@ class PortMapperImpl implements PortMapper {
|
||||
succeeded = gateway.addPortMapping(port, port,
|
||||
getHostAddress(internal), "TCP", "TCP");
|
||||
if (succeeded) {
|
||||
shutdownManager.addShutdownHook(() -> deleteMapping(port));
|
||||
shutdownManager.addShutdownHook(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deleteMapping(port);
|
||||
}
|
||||
});
|
||||
}
|
||||
String externalString = gateway.getExternalIPAddress();
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -58,7 +63,9 @@ class PortMapperImpl implements PortMapper {
|
||||
"External address " + scrubInetAddress(externalString));
|
||||
if (externalString != null)
|
||||
external = InetAddress.getByName(externalString);
|
||||
} catch (IOException | SAXException e) {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (SAXException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
return new MappingResult(internal, external, port, succeeded);
|
||||
@@ -75,7 +82,11 @@ class PortMapperImpl implements PortMapper {
|
||||
GatewayDiscover d = new GatewayDiscover();
|
||||
try {
|
||||
d.discover();
|
||||
} catch (IOException | SAXException | ParserConfigurationException e) {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (SAXException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (ParserConfigurationException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
gateway = d.getValidGateway();
|
||||
@@ -86,7 +97,9 @@ class PortMapperImpl implements PortMapper {
|
||||
gateway.deletePortMapping(port, "TCP");
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Deleted mapping for port " + port);
|
||||
} catch (IOException | SAXException e) {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} catch (SAXException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,37 +110,41 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
protected void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
ServerSocket ss = null;
|
||||
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
||||
try {
|
||||
ss = new ServerSocket();
|
||||
ss.bind(addr);
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Failed to bind " + scrubSocketAddress(addr));
|
||||
tryToClose(ss);
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!running) return;
|
||||
ServerSocket ss = null;
|
||||
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
||||
try {
|
||||
ss = new ServerSocket();
|
||||
ss.bind(addr);
|
||||
break;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Failed to bind " +
|
||||
scrubSocketAddress(addr));
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
if (ss == null || !ss.isBound()) {
|
||||
LOG.info("Could not bind server socket");
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
InetSocketAddress local =
|
||||
(InetSocketAddress) ss.getLocalSocketAddress();
|
||||
setLocalSocketAddress(local);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Listening on " + scrubSocketAddress(local));
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
}
|
||||
if (ss == null || !ss.isBound()) {
|
||||
LOG.info("Could not bind server socket");
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
InetSocketAddress local =
|
||||
(InetSocketAddress) ss.getLocalSocketAddress();
|
||||
setLocalSocketAddress(local);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Listening on " + scrubSocketAddress(local));
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -214,13 +218,17 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void connectAndCallBack(ContactId c, TransportProperties p) {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
private void connectAndCallBack(final ContactId c,
|
||||
final TransportProperties p) {
|
||||
ioExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning()) return;
|
||||
DuplexTransportConnection d = createConnection(p);
|
||||
if (d != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, d);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -297,7 +305,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -309,7 +317,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<InetAddress> addrs = new ArrayList<>();
|
||||
List<InetAddress> addrs = new ArrayList<InetAddress>();
|
||||
for (NetworkInterface iface : ifaces)
|
||||
addrs.addAll(Collections.list(iface.getInetAddresses()));
|
||||
return addrs;
|
||||
|
||||
@@ -43,7 +43,7 @@ class WanTcpPlugin extends TcpPlugin {
|
||||
// Use the same address and port as last time if available
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT));
|
||||
List<InetSocketAddress> addrs = new LinkedList<>();
|
||||
List<InetSocketAddress> addrs = new LinkedList<InetSocketAddress>();
|
||||
for (InetAddress a : getLocalIpAddresses()) {
|
||||
if (isAcceptableAddress(a)) {
|
||||
// If this is the old address, try to use the same port
|
||||
|
||||
@@ -40,12 +40,9 @@ public class PropertiesModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
TransportPropertyManager getTransportPropertyManager(
|
||||
LifecycleManager lifecycleManager,
|
||||
ValidationManager validationManager, ContactManager contactManager,
|
||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||
TransportPropertyManagerImpl transportPropertyManager) {
|
||||
lifecycleManager.registerClient(transportPropertyManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
transportPropertyManager);
|
||||
contactManager.registerAddContactHook(transportPropertyManager);
|
||||
contactManager.registerRemoveContactHook(transportPropertyManager);
|
||||
return transportPropertyManager;
|
||||
|
||||
@@ -9,10 +9,8 @@ import org.briarproject.bramble.api.contact.ContactManager.AddContactHook;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
@@ -21,10 +19,8 @@ import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -40,22 +36,20 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
Client, AddContactHook, RemoveContactHook, IncomingMessageHook {
|
||||
Client, AddContactHook, RemoveContactHook {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final MetadataParser metadataParser;
|
||||
private final ContactGroupFactory contactGroupFactory;
|
||||
private final Clock clock;
|
||||
private final Group localGroup;
|
||||
|
||||
@Inject
|
||||
TransportPropertyManagerImpl(DatabaseComponent db,
|
||||
ClientHelper clientHelper, MetadataParser metadataParser,
|
||||
ContactGroupFactory contactGroupFactory, Clock clock) {
|
||||
ClientHelper clientHelper, ContactGroupFactory contactGroupFactory,
|
||||
Clock clock) {
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.metadataParser = metadataParser;
|
||||
this.contactGroupFactory = contactGroupFactory;
|
||||
this.clock = clock;
|
||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
|
||||
@@ -63,7 +57,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
db.addGroup(txn, localGroup);
|
||||
// Ensure we've set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
@@ -91,31 +84,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
db.removeGroup(txn, getContactGroup(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||
throws DbException, InvalidMessageException {
|
||||
try {
|
||||
// Find the latest update for this transport, if any
|
||||
BdfDictionary d = metadataParser.parse(meta);
|
||||
TransportId t = new TransportId(d.getString("transportId"));
|
||||
LatestUpdate latest = findLatest(txn, m.getGroupId(), t, false);
|
||||
if (latest != null) {
|
||||
if (d.getLong("version") > latest.version) {
|
||||
// This update is newer - delete the previous update
|
||||
db.deleteMessage(txn, latest.messageId);
|
||||
db.deleteMessageMetadata(txn, latest.messageId);
|
||||
} else {
|
||||
// We've already received a newer update - delete this one
|
||||
db.deleteMessage(txn, m.getId());
|
||||
db.deleteMessageMetadata(txn, m.getId());
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRemoteProperties(Transaction txn, ContactId c,
|
||||
Map<TransportId, TransportProperties> props) throws DbException {
|
||||
@@ -130,8 +98,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public Map<TransportId, TransportProperties> getLocalProperties()
|
||||
throws DbException {
|
||||
Map<TransportId, TransportProperties> local;
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
local = getLocalProperties(txn);
|
||||
db.commitTransaction(txn);
|
||||
@@ -145,9 +112,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public Map<TransportId, TransportProperties> getLocalProperties(
|
||||
Transaction txn) throws DbException {
|
||||
try {
|
||||
Map<TransportId, TransportProperties> local = new HashMap<>();
|
||||
Map<TransportId, TransportProperties> local =
|
||||
new HashMap<TransportId, TransportProperties>();
|
||||
// Find the latest local update for each transport
|
||||
Map<TransportId, LatestUpdate> latest = findLatestLocal(txn);
|
||||
Map<TransportId, LatestUpdate> latest = findLatest(txn,
|
||||
localGroup.getId(), true);
|
||||
// Retrieve and parse the latest local properties
|
||||
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
|
||||
BdfList message = clientHelper.getMessageAsList(txn,
|
||||
@@ -166,8 +135,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
throws DbException {
|
||||
try {
|
||||
TransportProperties p = null;
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
// Find the latest local update
|
||||
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
|
||||
@@ -192,9 +160,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Override
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties(
|
||||
TransportId t) throws DbException {
|
||||
Map<ContactId, TransportProperties> remote = new HashMap<>();
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
new HashMap<ContactId, TransportProperties>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
for (Contact c : db.getContacts(txn))
|
||||
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
||||
@@ -228,8 +196,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
TransportProperties p;
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
p = getRemoteProperties(txn, db.getContact(txn, c), t);
|
||||
db.commitTransaction(txn);
|
||||
@@ -267,9 +234,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
long version = latest == null ? 1 : latest.version + 1;
|
||||
storeMessage(txn, localGroup.getId(), t, merged, version,
|
||||
true, false);
|
||||
// Delete the previous update, if any
|
||||
if (latest != null)
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
// Store the merged properties in each contact's group
|
||||
for (Contact c : db.getContacts(txn)) {
|
||||
Group g = getContactGroup(c);
|
||||
@@ -277,9 +241,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
version = latest == null ? 1 : latest.version + 1;
|
||||
storeMessage(txn, g.getId(), t, merged, version,
|
||||
true, true);
|
||||
// Delete the previous update, if any
|
||||
if (latest != null)
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
}
|
||||
}
|
||||
db.commitTransaction(txn);
|
||||
@@ -317,26 +278,20 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
return BdfList.of(t.getString(), version, p);
|
||||
}
|
||||
|
||||
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
|
||||
throws DbException, FormatException {
|
||||
// TODO: This can be simplified before 1.0
|
||||
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
|
||||
Map<MessageId, BdfDictionary> metadata = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId());
|
||||
private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
|
||||
GroupId g, boolean local) throws DbException, FormatException {
|
||||
Map<TransportId, LatestUpdate> latestUpdates =
|
||||
new HashMap<TransportId, LatestUpdate>();
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||
BdfDictionary meta = e.getValue();
|
||||
TransportId t = new TransportId(meta.getString("transportId"));
|
||||
long version = meta.getLong("version");
|
||||
LatestUpdate latest = latestUpdates.get(t);
|
||||
if (latest == null) {
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
} else if (version > latest.version) {
|
||||
// This update is newer - delete the previous one
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
} else {
|
||||
// We've already found a newer update - delete this one
|
||||
db.removeMessage(txn, e.getKey());
|
||||
if (meta.getBoolean("local") == local) {
|
||||
TransportId t = new TransportId(meta.getString("transportId"));
|
||||
long version = meta.getLong("version");
|
||||
LatestUpdate latest = latestUpdates.get(t);
|
||||
if (latest == null || version > latest.version)
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
}
|
||||
}
|
||||
return latestUpdates;
|
||||
@@ -345,7 +300,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Nullable
|
||||
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
||||
boolean local) throws DbException, FormatException {
|
||||
// TODO: This can be simplified before 1.0
|
||||
LatestUpdate latest = null;
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
@@ -354,26 +308,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
if (meta.getString("transportId").equals(t.getString())
|
||||
&& meta.getBoolean("local") == local) {
|
||||
long version = meta.getLong("version");
|
||||
if (latest == null) {
|
||||
if (latest == null || version > latest.version)
|
||||
latest = new LatestUpdate(e.getKey(), version);
|
||||
} else if (version > latest.version) {
|
||||
// This update is newer - delete the previous one
|
||||
if (local) {
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
} else {
|
||||
db.deleteMessage(txn, latest.messageId);
|
||||
db.deleteMessageMetadata(txn, latest.messageId);
|
||||
}
|
||||
latest = new LatestUpdate(e.getKey(), version);
|
||||
} else {
|
||||
// We've already found a newer update - delete this one
|
||||
if (local) {
|
||||
db.removeMessage(txn, e.getKey());
|
||||
} else {
|
||||
db.deleteMessage(txn, e.getKey());
|
||||
db.deleteMessageMetadata(txn, e.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return latest;
|
||||
|
||||
@@ -41,7 +41,7 @@ class Receiver implements ReadHandler {
|
||||
Receiver(Clock clock, Sender sender) {
|
||||
this.sender = sender;
|
||||
this.clock = clock;
|
||||
dataFrames = new TreeSet<>(new SequenceNumberComparator());
|
||||
dataFrames = new TreeSet<Data>(new SequenceNumberComparator());
|
||||
}
|
||||
|
||||
Data read() throws IOException, InterruptedException {
|
||||
|
||||
@@ -42,44 +42,48 @@ class ReliabilityLayerImpl implements ReliabilityLayer, WriteHandler {
|
||||
this.executor = executor;
|
||||
this.clock = clock;
|
||||
this.writeHandler = writeHandler;
|
||||
writes = new LinkedBlockingQueue<>();
|
||||
writes = new LinkedBlockingQueue<byte[]>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
SlipEncoder encoder = new SlipEncoder(this);
|
||||
Sender sender = new Sender(clock, encoder);
|
||||
final Sender sender = new Sender(clock, encoder);
|
||||
receiver = new Receiver(clock, sender);
|
||||
decoder = new SlipDecoder(receiver, Data.MAX_LENGTH);
|
||||
inputStream = new ReceiverInputStream(receiver);
|
||||
outputStream = new SenderOutputStream(sender);
|
||||
running = true;
|
||||
executor.execute(() -> {
|
||||
long now = clock.currentTimeMillis();
|
||||
long next = now + TICK_INTERVAL;
|
||||
try {
|
||||
while (running) {
|
||||
byte[] b = null;
|
||||
while (now < next && b == null) {
|
||||
b = writes.poll(next - now, MILLISECONDS);
|
||||
if (!running) return;
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
if (b == null) {
|
||||
sender.tick();
|
||||
while (next <= now) next += TICK_INTERVAL;
|
||||
} else {
|
||||
if (b.length == 0) return; // Poison pill
|
||||
writeHandler.handleWrite(b);
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long now = clock.currentTimeMillis();
|
||||
long next = now + TICK_INTERVAL;
|
||||
try {
|
||||
while (running) {
|
||||
byte[] b = null;
|
||||
while (now < next && b == null) {
|
||||
b = writes.poll(next - now, MILLISECONDS);
|
||||
if (!running) return;
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
if (b == null) {
|
||||
sender.tick();
|
||||
while (next <= now) next += TICK_INTERVAL;
|
||||
} else {
|
||||
if (b.length == 0) return; // Poison pill
|
||||
writeHandler.handleWrite(b);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting to write");
|
||||
Thread.currentThread().interrupt();
|
||||
running = false;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
running = false;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warning("Interrupted while waiting to write");
|
||||
Thread.currentThread().interrupt();
|
||||
running = false;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
running = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class Sender {
|
||||
Sender(Clock clock, WriteHandler writeHandler) {
|
||||
this.clock = clock;
|
||||
this.writeHandler = writeHandler;
|
||||
outstanding = new LinkedList<>();
|
||||
outstanding = new LinkedList<Outstanding>();
|
||||
}
|
||||
|
||||
void sendAck(long sequenceNumber, int windowSize) throws IOException {
|
||||
@@ -136,7 +136,7 @@ class Sender {
|
||||
if (now - o.lastTransmitted > rto) {
|
||||
it.remove();
|
||||
if (retransmit == null)
|
||||
retransmit = new ArrayList<>();
|
||||
retransmit = new ArrayList<Outstanding>();
|
||||
retransmit.add(o);
|
||||
// Update the retransmission timeout
|
||||
rto <<= 1;
|
||||
|
||||
@@ -10,7 +10,7 @@ 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.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
@@ -29,8 +29,6 @@ import java.util.Collection;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
@@ -38,7 +36,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
@@ -52,14 +49,17 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD
|
||||
@NotNullByDefault
|
||||
class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
// Check for retransmittable records once every 60 seconds
|
||||
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
||||
|
||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
||||
};
|
||||
private static final ThrowingRunnable<IOException>
|
||||
NEXT_SEND_TIME_DECREASED = () -> {
|
||||
};
|
||||
private static final ThrowingRunnable<IOException> CLOSE =
|
||||
new ThrowingRunnable<IOException>() {
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
};
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor dbExecutor;
|
||||
@@ -70,13 +70,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final RecordWriter recordWriter;
|
||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||
|
||||
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
||||
private final AtomicBoolean generateBatchQueued = new AtomicBoolean(false);
|
||||
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
|
||||
private final AtomicBoolean generateRequestQueued =
|
||||
new AtomicBoolean(false);
|
||||
private final AtomicLong nextSendTime = new AtomicLong(Long.MAX_VALUE);
|
||||
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
@@ -90,7 +83,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
this.maxLatency = maxLatency;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
this.recordWriter = recordWriter;
|
||||
writerTasks = new LinkedBlockingQueue<>();
|
||||
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
@@ -99,21 +92,21 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
// Start a query for each type of record
|
||||
generateAck();
|
||||
generateBatch();
|
||||
generateOffer();
|
||||
generateRequest();
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
long now = clock.currentTimeMillis();
|
||||
long nextKeepalive = now + maxIdleTime;
|
||||
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
boolean dataToFlush = true;
|
||||
// Write records until interrupted
|
||||
try {
|
||||
while (!interrupted) {
|
||||
// Work out how long we should wait for a record
|
||||
now = clock.currentTimeMillis();
|
||||
long keepaliveWait = Math.max(0, nextKeepalive - now);
|
||||
long sendWait = Math.max(0, nextSendTime.get() - now);
|
||||
long wait = Math.min(keepaliveWait, sendWait);
|
||||
long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
|
||||
if (wait < 0) wait = 0;
|
||||
// Flush any unflushed data if we're going to wait
|
||||
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
||||
recordWriter.flush();
|
||||
@@ -125,25 +118,20 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
MILLISECONDS);
|
||||
if (task == null) {
|
||||
now = clock.currentTimeMillis();
|
||||
if (now >= nextSendTime.get()) {
|
||||
// Check for retransmittable messages
|
||||
LOG.info("Checking for retransmittable messages");
|
||||
setNextSendTime(Long.MAX_VALUE);
|
||||
generateBatch();
|
||||
generateOffer();
|
||||
if (now >= nextRetxQuery) {
|
||||
// Check for retransmittable records
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
}
|
||||
if (now >= nextKeepalive) {
|
||||
// Flush the stream to keep it alive
|
||||
LOG.info("Sending keepalive");
|
||||
recordWriter.flush();
|
||||
dataToFlush = false;
|
||||
nextKeepalive = now + maxIdleTime;
|
||||
}
|
||||
} else if (task == CLOSE) {
|
||||
LOG.info("Closed");
|
||||
break;
|
||||
} else if (task == NEXT_SEND_TIME_DECREASED) {
|
||||
LOG.info("Next send time decreased");
|
||||
} else {
|
||||
task.run();
|
||||
dataToFlush = true;
|
||||
@@ -159,31 +147,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void generateAck() {
|
||||
if (generateAckQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
}
|
||||
|
||||
private void generateBatch() {
|
||||
if (generateBatchQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
|
||||
private void generateOffer() {
|
||||
if (generateOfferQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
|
||||
private void generateRequest() {
|
||||
if (generateRequestQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
}
|
||||
|
||||
private void setNextSendTime(long time) {
|
||||
long old = nextSendTime.getAndSet(time);
|
||||
if (time < old) writerTasks.add(NEXT_SEND_TIME_DECREASED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
interrupted = true;
|
||||
@@ -196,23 +159,22 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof MessageSharedEvent) {
|
||||
generateOffer();
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getAffectedContacts().contains(contactId))
|
||||
generateOffer();
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if (e instanceof MessageRequestedEvent) {
|
||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||
generateBatch();
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
} else if (e instanceof MessageToAckEvent) {
|
||||
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
||||
generateAck();
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
} else if (e instanceof MessageToRequestEvent) {
|
||||
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
||||
generateRequest();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +184,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateAckQueued.getAndSet(false)) throw new AssertionError();
|
||||
try {
|
||||
Ack a;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
@@ -256,7 +217,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
recordWriter.writeAck(ack);
|
||||
LOG.info("Sent ack");
|
||||
generateAck();
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,15 +227,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateBatchQueued.getAndSet(false))
|
||||
throw new AssertionError();
|
||||
try {
|
||||
Collection<byte[]> b;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
b = db.generateRequestedBatch(txn, contactId,
|
||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -303,7 +261,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
||||
LOG.info("Sent batch");
|
||||
generateBatch();
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,15 +271,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateOfferQueued.getAndSet(false))
|
||||
throw new AssertionError();
|
||||
try {
|
||||
Offer o;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
|
||||
maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -350,7 +305,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
recordWriter.writeOffer(offer);
|
||||
LOG.info("Sent offer");
|
||||
generateOffer();
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,8 +315,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateRequestQueued.getAndSet(false))
|
||||
throw new AssertionError();
|
||||
try {
|
||||
Request r;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
@@ -395,7 +348,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
recordWriter.writeRequest(request);
|
||||
LOG.info("Sent request");
|
||||
generateRequest();
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ 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.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
|
||||
/**
|
||||
* An incoming {@link SyncSession}.
|
||||
@@ -97,9 +96,8 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ class RecordReaderImpl implements RecordReader {
|
||||
private List<MessageId> readMessageIds() throws IOException {
|
||||
if (payloadLength == 0) throw new FormatException();
|
||||
if (payloadLength % UniqueId.LENGTH != 0) throw new FormatException();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
List<MessageId> ids = new ArrayList<MessageId>();
|
||||
for (int off = 0; off < payloadLength; off += UniqueId.LENGTH) {
|
||||
byte[] id = new byte[UniqueId.LENGTH];
|
||||
System.arraycopy(payload, off, id, 0, UniqueId.LENGTH);
|
||||
|
||||
@@ -10,7 +10,7 @@ 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.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
@@ -28,7 +28,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
@@ -44,7 +43,12 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SimplexOutgoingSession.class.getName());
|
||||
|
||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {};
|
||||
private static final ThrowingRunnable<IOException> CLOSE =
|
||||
new ThrowingRunnable<IOException>() {
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
};
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor dbExecutor;
|
||||
@@ -67,7 +71,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
this.maxLatency = maxLatency;
|
||||
this.recordWriter = recordWriter;
|
||||
outstandingQueries = new AtomicInteger(2); // One per type of record
|
||||
writerTasks = new LinkedBlockingQueue<>();
|
||||
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
@@ -110,9 +114,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.validationExecutor = validationExecutor;
|
||||
this.messageFactory = messageFactory;
|
||||
validators = new ConcurrentHashMap<>();
|
||||
hooks = new ConcurrentHashMap<>();
|
||||
validators = new ConcurrentHashMap<ClientId, MessageValidator>();
|
||||
hooks = new ConcurrentHashMap<ClientId, IncomingMessageHook>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,14 +93,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
hooks.put(c, hook);
|
||||
}
|
||||
|
||||
private void validateOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> validateOutstandingMessages(c));
|
||||
private void validateOutstandingMessagesAsync(final ClientId c) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
validateOutstandingMessages(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void validateOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> unvalidated = new LinkedList<>();
|
||||
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
unvalidated.addAll(db.getMessagesToValidate(txn, c));
|
||||
@@ -114,9 +119,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNextMessageAsync(Queue<MessageId> unvalidated) {
|
||||
private void validateNextMessageAsync(final Queue<MessageId> unvalidated) {
|
||||
if (unvalidated.isEmpty()) return;
|
||||
dbExecutor.execute(() -> validateNextMessage(unvalidated));
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
validateNextMessage(unvalidated);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@@ -148,14 +158,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> deliverOutstandingMessages(c));
|
||||
private void deliverOutstandingMessagesAsync(final ClientId c) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deliverOutstandingMessages(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void deliverOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> pending = new LinkedList<>();
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
pending.addAll(db.getPendingMessages(txn, c));
|
||||
@@ -169,9 +184,15 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverNextPendingMessageAsync(Queue<MessageId> pending) {
|
||||
private void deliverNextPendingMessageAsync(
|
||||
final Queue<MessageId> pending) {
|
||||
if (pending.isEmpty()) return;
|
||||
dbExecutor.execute(() -> deliverNextPendingMessage(pending));
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
deliverNextPendingMessage(pending);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@@ -208,7 +229,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
pending.addAll(getPendingDependents(txn, id));
|
||||
if (result.share) {
|
||||
db.setMessageShared(txn, id);
|
||||
toShare = new LinkedList<>(states.keySet());
|
||||
toShare = new LinkedList<MessageId>(
|
||||
states.keySet());
|
||||
}
|
||||
} else {
|
||||
invalidate = getDependentsToInvalidate(txn, id);
|
||||
@@ -233,8 +255,13 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void validateMessageAsync(Message m, Group g) {
|
||||
validationExecutor.execute(() -> validateMessage(m, g));
|
||||
private void validateMessageAsync(final Message m, final Group g) {
|
||||
validationExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
validateMessage(m, g);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ValidationExecutor
|
||||
@@ -250,16 +277,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
} catch (InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.log(INFO, e.toString(), e);
|
||||
Queue<MessageId> invalidate = new LinkedList<>();
|
||||
Queue<MessageId> invalidate = new LinkedList<MessageId>();
|
||||
invalidate.add(m.getId());
|
||||
invalidateNextMessageAsync(invalidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void storeMessageContextAsync(Message m, ClientId c,
|
||||
MessageContext result) {
|
||||
dbExecutor.execute(() -> storeMessageContext(m, c, result));
|
||||
private void storeMessageContextAsync(final Message m, final ClientId c,
|
||||
final MessageContext result) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
storeMessageContext(m, c, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@@ -299,7 +331,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
pending = getPendingDependents(txn, id);
|
||||
if (result.share) {
|
||||
db.setMessageShared(txn, id);
|
||||
toShare = new LinkedList<>(dependencies);
|
||||
toShare =
|
||||
new LinkedList<MessageId>(dependencies);
|
||||
}
|
||||
} else {
|
||||
invalidate = getDependentsToInvalidate(txn, id);
|
||||
@@ -345,7 +378,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
@DatabaseExecutor
|
||||
private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m)
|
||||
throws DbException {
|
||||
Queue<MessageId> pending = new LinkedList<>();
|
||||
Queue<MessageId> pending = new LinkedList<MessageId>();
|
||||
Map<MessageId, State> states = db.getMessageDependents(txn, m);
|
||||
for (Entry<MessageId, State> e : states.entrySet()) {
|
||||
if (e.getValue() == PENDING) pending.add(e.getKey());
|
||||
@@ -353,14 +386,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
return pending;
|
||||
}
|
||||
|
||||
private void shareOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> shareOutstandingMessages(c));
|
||||
private void shareOutstandingMessagesAsync(final ClientId c) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
shareOutstandingMessages(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void shareOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> toShare = new LinkedList<>();
|
||||
Queue<MessageId> toShare = new LinkedList<MessageId>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
toShare.addAll(db.getMessagesToShare(txn, c));
|
||||
@@ -380,9 +418,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
* This method should only be called for messages that have all their
|
||||
* dependencies delivered and have been delivered themselves.
|
||||
*/
|
||||
private void shareNextMessageAsync(Queue<MessageId> toShare) {
|
||||
private void shareNextMessageAsync(final Queue<MessageId> toShare) {
|
||||
if (toShare.isEmpty()) return;
|
||||
dbExecutor.execute(() -> shareNextMessage(toShare));
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
shareNextMessage(toShare);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@@ -409,9 +452,14 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateNextMessageAsync(Queue<MessageId> invalidate) {
|
||||
private void invalidateNextMessageAsync(final Queue<MessageId> invalidate) {
|
||||
if (invalidate.isEmpty()) return;
|
||||
dbExecutor.execute(() -> invalidateNextMessage(invalidate));
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
invalidateNextMessage(invalidate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@@ -448,7 +496,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
@DatabaseExecutor
|
||||
private Queue<MessageId> getDependentsToInvalidate(Transaction txn,
|
||||
MessageId m) throws DbException {
|
||||
Queue<MessageId> invalidate = new LinkedList<>();
|
||||
Queue<MessageId> invalidate = new LinkedList<MessageId>();
|
||||
Map<MessageId, State> states = db.getMessageDependents(txn, m);
|
||||
for (Entry<MessageId, State> e : states.entrySet()) {
|
||||
if (e.getValue() != INVALID) invalidate.add(e.getKey());
|
||||
@@ -466,12 +514,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGroupAndValidateAsync(Message m) {
|
||||
dbExecutor.execute(() -> loadGroupAndValidate(m));
|
||||
private void loadGroupAndValidateAsync(final Message m) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadGroupAndValidate(m);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void loadGroupAndValidate(Message m) {
|
||||
private void loadGroupAndValidate(final Message m) {
|
||||
try {
|
||||
Group g;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
|
||||
@@ -58,14 +58,15 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
this.pluginConfig = pluginConfig;
|
||||
this.transportKeyManagerFactory = transportKeyManagerFactory;
|
||||
// Use a ConcurrentHashMap as a thread-safe set
|
||||
activeContacts = new ConcurrentHashMap<>();
|
||||
managers = new ConcurrentHashMap<>();
|
||||
activeContacts = new ConcurrentHashMap<ContactId, Boolean>();
|
||||
managers = new ConcurrentHashMap<TransportId, TransportKeyManager>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
Map<TransportId, Integer> transports = new HashMap<>();
|
||||
Map<TransportId, Integer> transports =
|
||||
new HashMap<TransportId, Integer>();
|
||||
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
|
||||
transports.put(f.getId(), f.getMaxLatency());
|
||||
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories())
|
||||
@@ -155,10 +156,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void removeContact(ContactId c) {
|
||||
private void removeContact(final ContactId c) {
|
||||
activeContacts.remove(c);
|
||||
dbExecutor.execute(() -> {
|
||||
for (TransportKeyManager m : managers.values()) m.removeContact(c);
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (TransportKeyManager m : managers.values())
|
||||
m.removeContact(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class ReorderingWindow {
|
||||
}
|
||||
|
||||
List<Long> getUnseen() {
|
||||
List<Long> unseen = new ArrayList<>(seen.length);
|
||||
List<Long> unseen = new ArrayList<Long>(seen.length);
|
||||
for (int i = 0; i < seen.length; i++)
|
||||
if (!seen[i]) unseen.add(base + i);
|
||||
return unseen;
|
||||
@@ -69,8 +69,8 @@ class ReorderingWindow {
|
||||
return new Change(added, removed);
|
||||
}
|
||||
// Record the elements that will be added and removed
|
||||
List<Long> added = new ArrayList<>(slide);
|
||||
List<Long> removed = new ArrayList<>(slide);
|
||||
List<Long> added = new ArrayList<Long>(slide);
|
||||
List<Long> removed = new ArrayList<Long>(slide);
|
||||
for (int i = 0; i < slide; i++) {
|
||||
if (!seen[i]) removed.add(base + i);
|
||||
added.add(base + seen.length + i);
|
||||
|
||||
@@ -65,9 +65,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
this.transportId = transportId;
|
||||
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||
lock = new ReentrantLock();
|
||||
inContexts = new HashMap<>();
|
||||
outContexts = new HashMap<>();
|
||||
keys = new HashMap<>();
|
||||
inContexts = new HashMap<Bytes, TagContext>();
|
||||
outContexts = new HashMap<ContactId, MutableOutgoingKeys>();
|
||||
keys = new HashMap<ContactId, MutableTransportKeys>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,22 +134,32 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
|
||||
private void scheduleKeyRotation(long now) {
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
rotateKeys();
|
||||
}
|
||||
};
|
||||
long delay = rotationPeriodLength - now % rotationPeriodLength;
|
||||
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS);
|
||||
scheduler.schedule(task, delay, MILLISECONDS);
|
||||
}
|
||||
|
||||
private void rotateKeys() {
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
rotateKeys(txn);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
rotateKeys(txn);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -262,7 +272,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
lock.lock();
|
||||
try {
|
||||
// Rotate the keys to the current rotation period
|
||||
Map<ContactId, TransportKeys> snapshot = new HashMap<>();
|
||||
Map<ContactId, TransportKeys> snapshot =
|
||||
new HashMap<ContactId, TransportKeys>();
|
||||
for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet())
|
||||
snapshot.put(e.getKey(), e.getValue().snapshot());
|
||||
RotationResult rotationResult = rotateKeys(snapshot, now);
|
||||
@@ -300,8 +311,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
private final Map<ContactId, TransportKeys> current, rotated;
|
||||
|
||||
private RotationResult() {
|
||||
current = new HashMap<>();
|
||||
rotated = new HashMap<>();
|
||||
current = new HashMap<ContactId, TransportKeys>();
|
||||
rotated = new HashMap<ContactId, TransportKeys>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,13 +24,16 @@ public class PoliteExecutorTest extends BrambleTestCase {
|
||||
Executor delegate = Executors.newSingleThreadExecutor();
|
||||
// Allow all the tasks to be delegated straight away
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
|
||||
List<Integer> list = new Vector<>();
|
||||
CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
int result = i;
|
||||
polite.execute(() -> {
|
||||
list.add(result);
|
||||
latch.countDown();
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
list.add(result);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
@@ -46,13 +49,16 @@ public class PoliteExecutorTest extends BrambleTestCase {
|
||||
Executor delegate = Executors.newSingleThreadExecutor();
|
||||
// Allow two tasks to be delegated at a time
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 2);
|
||||
List<Integer> list = new Vector<>();
|
||||
CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
int result = i;
|
||||
polite.execute(() -> {
|
||||
list.add(result);
|
||||
latch.countDown();
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
list.add(result);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
@@ -67,20 +73,23 @@ public class PoliteExecutorTest extends BrambleTestCase {
|
||||
Executor delegate = Executors.newCachedThreadPool();
|
||||
// Allow all the tasks to be delegated straight away
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
|
||||
List<Integer> list = new Vector<>();
|
||||
CountDownLatch[] latches = new CountDownLatch[TASKS];
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch[] latches = new CountDownLatch[TASKS];
|
||||
for (int i = 0; i < TASKS; i++) latches[i] = new CountDownLatch(1);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
int result = i;
|
||||
polite.execute(() -> {
|
||||
try {
|
||||
// Each task waits for the next task, if any, to finish
|
||||
if (result < TASKS - 1) latches[result + 1].await();
|
||||
list.add(result);
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Each task waits for the next task, if any, to finish
|
||||
if (result < TASKS - 1) latches[result + 1].await();
|
||||
list.add(result);
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
latches[result].countDown();
|
||||
}
|
||||
latches[result].countDown();
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
@@ -95,19 +104,22 @@ public class PoliteExecutorTest extends BrambleTestCase {
|
||||
Executor delegate = Executors.newCachedThreadPool();
|
||||
// Allow one task to be delegated at a time
|
||||
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 1);
|
||||
List<Integer> list = new Vector<>();
|
||||
CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
final List<Integer> list = new Vector<Integer>();
|
||||
final CountDownLatch latch = new CountDownLatch(TASKS);
|
||||
for (int i = 0; i < TASKS; i++) {
|
||||
int result = i;
|
||||
polite.execute(() -> {
|
||||
try {
|
||||
// Each task runs faster than the previous task
|
||||
Thread.sleep(TASKS - result);
|
||||
list.add(result);
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
final int result = i;
|
||||
polite.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Each task runs faster than the previous task
|
||||
Thread.sleep(TASKS - result);
|
||||
list.add(result);
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
// Wait for all the tasks to finish
|
||||
|
||||
@@ -83,9 +83,9 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsTooShortMessage() throws Exception {
|
||||
byte[] invalidRaw = new byte[MESSAGE_HEADER_LENGTH];
|
||||
final byte[] invalidRaw = new byte[MESSAGE_HEADER_LENGTH];
|
||||
// Use a mock message so the length of the raw message can be invalid
|
||||
Message invalidMessage = context.mock(Message.class);
|
||||
final Message invalidMessage = context.mock(Message.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(invalidMessage).getTimestamp();
|
||||
@@ -101,8 +101,8 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
|
||||
|
||||
@Test
|
||||
public void testAcceptsMinLengthMessage() throws Exception {
|
||||
byte[] shortRaw = new byte[MESSAGE_HEADER_LENGTH + 1];
|
||||
Message shortMessage =
|
||||
final byte[] shortRaw = new byte[MESSAGE_HEADER_LENGTH + 1];
|
||||
final Message shortMessage =
|
||||
new Message(messageId, groupId, timestamp, shortRaw);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
|
||||
@@ -76,8 +76,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testAddLocalMessage() throws Exception {
|
||||
boolean shared = true;
|
||||
Transaction txn = new Transaction(null, false);
|
||||
final boolean shared = true;
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
@@ -95,7 +95,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testCreateMessage() throws Exception {
|
||||
byte[] bytes = expectToByteArray(list);
|
||||
final byte[] bytes = expectToByteArray(list);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageFactory).createMessage(groupId, timestamp, bytes);
|
||||
@@ -107,7 +107,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetMessageAsList() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
|
||||
expectToList(true);
|
||||
context.checking(new Expectations() {{
|
||||
@@ -125,7 +125,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetGroupMetadataAsDictionary() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -145,7 +145,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetMessageMetadataAsDictionary() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -165,9 +165,10 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetMessageMetadataAsDictionaryMap() throws Exception {
|
||||
Map<MessageId, BdfDictionary> map = new HashMap<>();
|
||||
final Map<MessageId, BdfDictionary> map =
|
||||
new HashMap<MessageId, BdfDictionary>();
|
||||
map.put(messageId, dictionary);
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -187,13 +188,14 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetMessageMetadataAsDictionaryQuery() throws Exception {
|
||||
Map<MessageId, BdfDictionary> map = new HashMap<>();
|
||||
final Map<MessageId, BdfDictionary> map =
|
||||
new HashMap<MessageId, BdfDictionary>();
|
||||
map.put(messageId, dictionary);
|
||||
BdfDictionary query =
|
||||
final BdfDictionary query =
|
||||
BdfDictionary.of(new BdfEntry("query", "me"));
|
||||
Metadata queryMetadata = new Metadata();
|
||||
final Metadata queryMetadata = new Metadata();
|
||||
queryMetadata.put("query", getRandomBytes(42));
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
@@ -215,7 +217,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMergeGroupMetadata() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
@@ -233,7 +235,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMergeMessageMetadata() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
@@ -280,10 +282,10 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testSign() throws Exception {
|
||||
byte[] privateKey = getRandomBytes(42);
|
||||
byte[] signed = getRandomBytes(42);
|
||||
final byte[] privateKey = getRandomBytes(42);
|
||||
final byte[] signed = getRandomBytes(42);
|
||||
|
||||
byte[] bytes = expectToByteArray(list);
|
||||
final byte[] bytes = expectToByteArray(list);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).sign(label, bytes, privateKey);
|
||||
will(returnValue(signed));
|
||||
@@ -295,8 +297,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testVerifySignature() throws Exception {
|
||||
byte[] publicKey = getRandomBytes(42);
|
||||
byte[] bytes = expectToByteArray(list);
|
||||
final byte[] publicKey = getRandomBytes(42);
|
||||
final byte[] bytes = expectToByteArray(list);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
|
||||
@@ -309,8 +311,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testVerifyWrongSignature() throws Exception {
|
||||
byte[] publicKey = getRandomBytes(42);
|
||||
byte[] bytes = expectToByteArray(list);
|
||||
final byte[] publicKey = getRandomBytes(42);
|
||||
final byte[] bytes = expectToByteArray(list);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
|
||||
@@ -327,8 +329,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] expectToByteArray(BdfList list) throws Exception {
|
||||
BdfWriter bdfWriter = context.mock(BdfWriter.class);
|
||||
private byte[] expectToByteArray(final BdfList list) throws Exception {
|
||||
final BdfWriter bdfWriter = context.mock(BdfWriter.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(bdfWriterFactory)
|
||||
@@ -339,8 +341,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
private void expectToList(boolean eof) throws Exception {
|
||||
BdfReader bdfReader = context.mock(BdfReader.class);
|
||||
private void expectToList(final boolean eof) throws Exception {
|
||||
final BdfReader bdfReader = context.mock(BdfReader.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(bdfReaderFactory)
|
||||
|
||||
@@ -46,10 +46,10 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testAddContact() throws Exception {
|
||||
SecretKey master = getSecretKey();
|
||||
long timestamp = 42;
|
||||
boolean alice = true;
|
||||
Transaction txn = new Transaction(null, false);
|
||||
final SecretKey master = getSecretKey();
|
||||
final long timestamp = 42;
|
||||
final boolean alice = true;
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
@@ -64,13 +64,14 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
assertEquals(contactId, contactManager.addContact(remote, local,
|
||||
master, timestamp, alice, verified, active));
|
||||
assertEquals(contactId, contactManager
|
||||
.addContact(remote, local, master, timestamp, alice, verified,
|
||||
active));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
@@ -85,8 +86,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetContactByAuthor() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Collection<Contact> contacts = Collections.singleton(contact);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Collection<Contact> contacts = Collections.singleton(contact);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
@@ -101,7 +102,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test(expected = NoSuchContactException.class)
|
||||
public void testGetContactByUnknownAuthor() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
@@ -115,8 +116,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test(expected = NoSuchContactException.class)
|
||||
public void testGetContactByUnknownLocalAuthor() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Collection<Contact> contacts = Collections.singleton(contact);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
final Collection<Contact> contacts = Collections.singleton(contact);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
@@ -131,9 +132,10 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testActiveContacts() throws Exception {
|
||||
Collection<Contact> activeContacts = Collections.singletonList(contact);
|
||||
Collection<Contact> contacts = new ArrayList<>(activeContacts);
|
||||
final Collection<Contact> contacts =
|
||||
new ArrayList<Contact>(activeContacts);
|
||||
contacts.add(new Contact(new ContactId(3), remote, local, true, false));
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
@@ -148,7 +150,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testRemoveContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
@@ -164,7 +166,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testSetContactActive() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).setContactActive(txn, contactId, active);
|
||||
}});
|
||||
@@ -174,7 +176,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testContactExists() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
|
||||
@@ -69,7 +69,7 @@ public class EllipticCurvePerformanceTest {
|
||||
ECPublicKeyParameters public2 =
|
||||
(ECPublicKeyParameters) keyPair2.getPublic();
|
||||
// Time some ECDH key agreements
|
||||
List<Long> samples = new ArrayList<>();
|
||||
List<Long> samples = new ArrayList<Long>();
|
||||
for (int i = 0; i < SAMPLES; i++) {
|
||||
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
||||
long start = System.nanoTime();
|
||||
@@ -79,7 +79,7 @@ public class EllipticCurvePerformanceTest {
|
||||
}
|
||||
long agreementMedian = median(samples);
|
||||
// Time some signatures
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
List<byte[]> signatures = new ArrayList<byte[]>();
|
||||
samples.clear();
|
||||
for (int i = 0; i < SAMPLES; i++) {
|
||||
Digest digest = new Blake2sDigest();
|
||||
|
||||
@@ -145,7 +145,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
private void assertAllDifferent(TransportKeys... transportKeys) {
|
||||
List<SecretKey> secretKeys = new ArrayList<>();
|
||||
List<SecretKey> secretKeys = new ArrayList<SecretKey>();
|
||||
for (TransportKeys k : transportKeys) {
|
||||
secretKeys.add(k.getPreviousIncomingKeys().getTagKey());
|
||||
secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey());
|
||||
@@ -160,7 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
private void assertAllDifferent(List<SecretKey> keys) {
|
||||
Set<Bytes> set = new HashSet<>();
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class TagEncodingTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testKeyAffectsTag() throws Exception {
|
||||
Set<Bytes> set = new HashSet<>();
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
SecretKey tagKey = TestUtils.getSecretKey();
|
||||
@@ -39,7 +39,7 @@ public class TagEncodingTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testProtocolVersionAffectsTag() throws Exception {
|
||||
Set<Bytes> set = new HashSet<>();
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
|
||||
@@ -49,7 +49,7 @@ public class TagEncodingTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testStreamNumberAffectsTag() throws Exception {
|
||||
Set<Bytes> set = new HashSet<>();
|
||||
Set<Bytes> set = new HashSet<Bytes>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.bramble.data;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -154,7 +155,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testWriteList() throws IOException {
|
||||
List<Object> l = new ArrayList<>();
|
||||
List<Object> l = new ArrayList<Object>();
|
||||
for (int i = 0; i < 3; i++) l.add(i);
|
||||
w.writeList(l);
|
||||
// LIST tag, elements as integers, END tag
|
||||
@@ -163,7 +164,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testListCanContainNull() throws IOException {
|
||||
List<Object> l = new ArrayList<>();
|
||||
List<Object> l = new ArrayList<Object>();
|
||||
l.add(1);
|
||||
l.add(null);
|
||||
l.add(NULL_VALUE);
|
||||
@@ -176,7 +177,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testWriteDictionary() throws IOException {
|
||||
// Use LinkedHashMap to get predictable iteration order
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
Map<String, Object> m = new LinkedHashMap<String, Object>();
|
||||
for (int i = 0; i < 4; i++) m.put(String.valueOf(i), i);
|
||||
w.writeDictionary(m);
|
||||
// DICTIONARY tag, keys as strings and values as integers, END tag
|
||||
@@ -215,12 +216,12 @@ public class BdfWriterImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testWriteNestedDictionariesAndLists() throws IOException {
|
||||
Map<String, Object> inner = new LinkedHashMap<>();
|
||||
Map<String, Object> inner = new LinkedHashMap<String, Object>();
|
||||
inner.put("bar", new byte[0]);
|
||||
List<Object> list = new ArrayList<>();
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
list.add(1);
|
||||
list.add(inner);
|
||||
Map<String, Object> outer = new LinkedHashMap<>();
|
||||
Map<String, Object> outer = new LinkedHashMap<String, Object>();
|
||||
outer.put("foo", list);
|
||||
w.writeDictionary(outer);
|
||||
// DICTIONARY tag, "foo" as string, LIST tag, 1 as integer,
|
||||
|
||||
@@ -99,7 +99,7 @@ public class MetadataEncoderParserIntegrationTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testList() throws FormatException {
|
||||
List<Long> l = new ArrayList<>(4);
|
||||
List<Long> l = new ArrayList<Long>(4);
|
||||
l.add(42L);
|
||||
l.add(1337L);
|
||||
l.add(Long.MIN_VALUE);
|
||||
@@ -114,7 +114,7 @@ public class MetadataEncoderParserIntegrationTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testDictionary() throws FormatException {
|
||||
Map<String, Boolean> m = new HashMap<>();
|
||||
Map<String, Boolean> m = new HashMap<String, Boolean>();
|
||||
m.put("1", true);
|
||||
m.put("2", false);
|
||||
|
||||
@@ -130,19 +130,19 @@ public class MetadataEncoderParserIntegrationTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testComplexDictionary() throws FormatException {
|
||||
Map<String, List> m = new HashMap<>();
|
||||
List<String> one = new ArrayList<>(3);
|
||||
Map<String, List> m = new HashMap<String, List>();
|
||||
List<String> one = new ArrayList<String>(3);
|
||||
one.add("\uFDD0");
|
||||
one.add("\uFDD1");
|
||||
one.add("\uFDD2");
|
||||
m.put("One", one);
|
||||
List<String> two = new ArrayList<>(2);
|
||||
List<String> two = new ArrayList<String>(2);
|
||||
two.add("\u0080");
|
||||
two.add("\uD800\uDC00");
|
||||
m.put("Two", two);
|
||||
d.put("test", m);
|
||||
|
||||
Map<String, Boolean> m2 = new HashMap<>();
|
||||
Map<String, Boolean> m2 = new HashMap<String, Boolean>();
|
||||
m2.put("should be true", true);
|
||||
d.put("another test", m2);
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ public class BasicH2Test extends BrambleTestCase {
|
||||
|
||||
private List<String> getNames() throws SQLException {
|
||||
String sql = "SELECT name FROM foo ORDER BY uniqueId";
|
||||
List<String> names = new ArrayList<>();
|
||||
List<String> names = new ArrayList<String>();
|
||||
try {
|
||||
PreparedStatement ps = connection.prepareStatement(sql);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
|
||||
@@ -47,10 +47,11 @@ import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -73,13 +74,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final Database<Object> database = context.mock(Database.class);
|
||||
private final ShutdownManager shutdown =
|
||||
context.mock(ShutdownManager.class);
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
public class DatabaseComponentImplTest extends BrambleTestCase {
|
||||
|
||||
private final Object txn = new Object();
|
||||
private final ClientId clientId;
|
||||
@@ -125,16 +120,21 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
private DatabaseComponent createDatabaseComponent(Database<Object> database,
|
||||
EventBus eventBus, ShutdownManager shutdown) {
|
||||
return new DatabaseComponentImpl<>(database, Object.class, eventBus,
|
||||
shutdown);
|
||||
return new DatabaseComponentImpl<Object>(database, Object.class,
|
||||
eventBus, shutdown);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSimpleCalls() throws Exception {
|
||||
int shutdownHandle = 12345;
|
||||
final int shutdownHandle = 12345;
|
||||
Mockery context = new Mockery();
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// open()
|
||||
oneOf(database).open(null);
|
||||
oneOf(database).open();
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
@@ -194,12 +194,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// close()
|
||||
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
||||
oneOf(database).close();
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.addLocalAuthor(transaction, localAuthor);
|
||||
@@ -220,11 +221,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
db.close();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalMessagesAreNotStoredUnlessGroupExists()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -244,10 +252,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLocalMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -279,11 +294,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfContactIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(18).of(database).startTransaction();
|
||||
@@ -478,11 +500,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the pseudonym is in the DB (which it's not)
|
||||
exactly(3).of(database).startTransaction();
|
||||
@@ -523,11 +552,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfGroupIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the group is in the DB (which it's not)
|
||||
exactly(8).of(database).startTransaction();
|
||||
@@ -621,11 +657,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfMessageIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the message is in the DB (which it's not)
|
||||
exactly(11).of(database).startTransaction();
|
||||
@@ -749,11 +792,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfTransportIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -840,12 +890,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAck() throws Exception {
|
||||
Collection<MessageId> messagesToAck = Arrays.asList(messageId,
|
||||
final Collection<MessageId> messagesToAck = Arrays.asList(messageId,
|
||||
messageId1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -868,13 +925,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateBatch() throws Exception {
|
||||
byte[] raw1 = new byte[size];
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||
final byte[] raw1 = new byte[size];
|
||||
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
final Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -905,12 +969,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateOffer() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -936,12 +1007,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateRequest() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -964,13 +1042,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateRequestedBatch() throws Exception {
|
||||
byte[] raw1 = new byte[size];
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||
final byte[] raw1 = new byte[size];
|
||||
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
final Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1002,10 +1087,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveAck() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1028,10 +1120,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1076,10 +1175,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveDuplicateMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1106,10 +1212,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveMessageWithoutVisibleGroup() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1129,13 +1242,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveOffer() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1176,10 +1296,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveRequest() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1203,10 +1330,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingVisibilityCallsListeners() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1236,11 +1370,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotChangingVisibilityDoesNotCallListeners()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1262,13 +1403,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys();
|
||||
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
||||
final TransportKeys transportKeys = createTransportKeys();
|
||||
final Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
||||
contactId, transportKeys);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1298,6 +1446,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys() {
|
||||
@@ -1322,14 +1472,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testMergeSettings() throws Exception {
|
||||
Settings before = new Settings();
|
||||
final Settings before = new Settings();
|
||||
before.put("foo", "bar");
|
||||
before.put("baz", "bam");
|
||||
Settings update = new Settings();
|
||||
final Settings update = new Settings();
|
||||
update.put("baz", "qux");
|
||||
Settings merged = new Settings();
|
||||
final Settings merged = new Settings();
|
||||
merged.put("foo", "bar");
|
||||
merged.put("baz", "qux");
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1359,6 +1514,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@@ -1388,6 +1545,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
private void testCannotStartTransactionDuringTransaction(
|
||||
boolean firstTxnReadOnly, boolean secondTxnReadOnly)
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1397,12 +1560,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
shutdown);
|
||||
|
||||
assertNotNull(db.startTransaction(firstTxnReadOnly));
|
||||
db.startTransaction(secondTxnReadOnly);
|
||||
fail();
|
||||
try {
|
||||
db.startTransaction(secondTxnReadOnly);
|
||||
fail();
|
||||
} finally {
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotAddLocalIdentityAsContact() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1426,10 +1599,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotAddDuplicateContact() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1455,16 +1636,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMessageDependencies() throws Exception {
|
||||
int shutdownHandle = 12345;
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
final int shutdownHandle = 12345;
|
||||
Mockery context = new Mockery();
|
||||
final Database<Object> database = context.mock(Database.class);
|
||||
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
final EventBus eventBus = context.mock(EventBus.class);
|
||||
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
context.checking(new Expectations() {{
|
||||
// open()
|
||||
oneOf(database).open(null);
|
||||
oneOf(database).open();
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
@@ -1506,16 +1693,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// close()
|
||||
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
||||
oneOf(database).close();
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.addLocalMessage(transaction, message, metadata, true);
|
||||
Collection<MessageId> dependencies = new ArrayList<>(2);
|
||||
Collection<MessageId> dependencies = new ArrayList<MessageId>(2);
|
||||
dependencies.add(messageId1);
|
||||
dependencies.add(messageId2);
|
||||
db.addMessageDependencies(transaction, message, dependencies);
|
||||
@@ -1526,5 +1714,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
db.close();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user