Compare commits

..

9 Commits

Author SHA1 Message Date
MajorCrazed
7e9153bd81 remove force disable 2017-11-06 16:56:30 +01:00
MajorCrazed
15fa44c1b1 remove comment 2017-11-06 16:43:02 +01:00
MajorCrazed
7ffe1b8bae move force variable to BluetoothDisableEvent 2017-11-02 13:31:21 +01:00
MajorCrazed
bb8713ddcb enable bluetooth by default 2017-11-02 13:30:54 +01:00
MajorCrazed
1e9afd14db set the reason to enable or disable bluetooth 2017-11-01 17:36:36 +01:00
MajorCrazed
86ea6eae63 handle different reasons why bluetooth should be enabled 2017-11-01 17:35:11 +01:00
MajorCrazed
56ba639084 enum to set the reason why bluetooth is enabled 2017-11-01 17:33:12 +01:00
MajorCrazed
8c16c2107f disable bluetooth after adding contact 2017-10-29 23:31:21 +01:00
MajorCrazed
cefe2b09e0 add option to force enable or disable bluetooth adapter 2017-10-29 23:30:47 +01:00
396 changed files with 8638 additions and 13120 deletions

View File

@@ -6,18 +6,8 @@ cache:
- .gradle/caches - .gradle/caches
before_script: before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle - export GRADLE_USER_HOME=$PWD/.gradle
# Accept the license for the Android build tools - echo y | /opt/android-sdk/tools/bin/sdkmanager "build-tools;23.0.3"
- 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
test: test:
script: script:

View File

@@ -6,90 +6,99 @@ apply plugin: 'witness'
apply plugin: 'de.undercouch.download' apply plugin: 'de.undercouch.download'
android { android {
compileSdkVersion 27 compileSdkVersion 23
buildToolsVersion '26.0.2' buildToolsVersion '25.0.0'
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 22
versionCode 1619 versionCode 1611
versionName "0.16.19" versionName "0.16.11"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_7
} }
} }
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') compile project(path: ':bramble-core', configuration: 'default')
implementation fileTree(dir: 'libs', include: '*.jar') compile fileTree(dir: 'libs', include: '*.jar')
provided 'javax.annotation:jsr250-api:1.0'
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
compileOnly 'javax.annotation:jsr250-api:1.0'
} }
dependencyVerification { def torBinaryDir = 'src/main/res/raw'
verify = [
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', task downloadTorGeoIp(type: Download) {
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3', src 'https://briarproject.org/build/geoip-2017-09-06.zip'
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b', dest "$torBinaryDir/geoip.zip"
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9', onlyIfNewer true
'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',
]
} }
ext.torBinaryDir = 'src/main/res/raw' task downloadTorBinaryArm(type: Download) {
ext.torVersion = '0.2.9.14' src 'https://briarproject.org/build/tor-0.2.9.12-arm.zip'
ext.geoipVersion = '2017-11-06' dest "$torBinaryDir/tor_arm.zip"
ext.torDownloadUrl = 'https://briarproject.org/build/' onlyIfNewer true
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
}
} }
def verifyBinary(name, chksum) { task downloadTorBinaryArmPie(type: Download) {
return tasks.create([ src 'https://briarproject.org/build/tor-0.2.9.12-arm-pie.zip'
name : "verifyBinary${name}", dest "$torBinaryDir/tor_arm_pie.zip"
type : Verify, onlyIfNewer true
dependsOn: downloadBinary(name)]) { }
src "${torBinaryDir}/${name}.zip"
algorithm 'SHA-256' task downloadTorBinaryX86(type: Download) {
checksum chksum 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 { project.afterEvaluate {
torBinaries.every { key, value -> preBuild.dependsOn {
preBuild.dependsOn.add(verifyBinary(key, value)) [
'verifyTorGeoIp',
'verifyTorBinaryArm',
'verifyTorBinaryArmPie',
'verifyTorBinaryX86',
'verifyTorBinaryX86Pie'
]
} }
} }

View File

@@ -13,8 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.TorPluginFactory; import org.briarproject.bramble.plugin.tor.TorPluginFactory;
@@ -23,7 +22,6 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@@ -35,21 +33,19 @@ public class AndroidPluginModule {
@Provides @Provides
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor, PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, SecureRandom random, AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory, SocketFactory torSocketFactory, BackoffFactory backoffFactory,
Application app, LocationUtils locationUtils, DevReporter reporter, Application app, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus) { EventBus eventBus) {
Context appContext = app.getApplicationContext(); Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, androidExecutor, appContext, random, eventBus, backoffFactory);
appContext, random, eventBus, backoffFactory); DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler, locationUtils, reporter, eventBus, torSocketFactory,
appContext, locationUtils, reporter, eventBus, backoffFactory);
torSocketFactory, backoffFactory);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
backoffFactory, appContext); backoffFactory, appContext);
Collection<DuplexPluginFactory> duplex = final Collection<DuplexPluginFactory> duplex =
Arrays.asList(bluetooth, tor, lan); Arrays.asList(bluetooth, tor, lan);
@NotNullByDefault @NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() { PluginConfig pluginConfig = new PluginConfig() {

View File

@@ -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");
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.plugin.bluetooth; package org.briarproject.bramble.plugin.droidtooth;
import android.content.Context; import android.content.Context;
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@Immutable @Immutable
@NotNullByDefault @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 MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
@@ -35,7 +35,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final EventBus eventBus; private final EventBus eventBus;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor, public DroidtoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext, AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, SecureRandom secureRandom, EventBus eventBus,
BackoffFactory backoffFactory) { BackoffFactory backoffFactory) {
@@ -61,7 +61,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor, DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
androidExecutor, appContext, secureRandom, backoff, callback, androidExecutor, appContext, secureRandom, backoff, callback,
MAX_LATENCY); MAX_LATENCY);
eventBus.addListener(plugin); eventBus.addListener(plugin);

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.plugin.bluetooth; package org.briarproject.bramble.plugin.droidtooth;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
@@ -11,12 +11,11 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@NotNullByDefault @NotNullByDefault
class AndroidBluetoothTransportConnection class DroidtoothTransportConnection extends AbstractDuplexTransportConnection {
extends AbstractDuplexTransportConnection {
private final BluetoothSocket socket; private final BluetoothSocket socket;
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) { DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
super(plugin); super(plugin);
this.socket = socket; this.socket = socket;
} }

View File

@@ -59,12 +59,7 @@ import java.util.Map.Entry;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; 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.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.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipInputStream; 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.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE; 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.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI; 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 android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MILLISECONDS; 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.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
@@ -112,7 +102,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Logger.getLogger(TorPlugin.class.getName()); Logger.getLogger(TorPlugin.class.getName());
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final DevReporter reporter; private final DevReporter reporter;
@@ -125,9 +114,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final File torDirectory, torFile, geoIpFile, configFile; private final File torDirectory, torFile, geoIpFile, configFile;
private final File doneFile, cookieFile; private final File doneFile, cookieFile;
private final PowerManager.WakeLock wakeLock; private final PowerManager.WakeLock wakeLock;
private final Lock connectionStatusLock;
private final AtomicReference<Future<?>> connectivityCheck =
new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false; private volatile boolean running = false;
@@ -136,13 +122,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile TorControlConnection controlConnection = null; private volatile TorControlConnection controlConnection = null;
private volatile BroadcastReceiver networkStateReceiver = null; private volatile BroadcastReceiver networkStateReceiver = null;
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler, TorPlugin(Executor ioExecutor, Context appContext,
Context appContext, LocationUtils locationUtils, LocationUtils locationUtils, DevReporter reporter,
DevReporter reporter, SocketFactory torSocketFactory, SocketFactory torSocketFactory, Backoff backoff,
Backoff backoff, DuplexPluginCallback callback, DuplexPluginCallback callback, String architecture, int maxLatency,
String architecture, int maxLatency, int maxIdleTime) { int maxIdleTime) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.reporter = reporter; this.reporter = reporter;
@@ -167,7 +152,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// This tag will prevent Huawei's powermanager from killing us. // This tag will prevent Huawei's powermanager from killing us.
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService"); wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
wakeLock.setReferenceCounted(false); wakeLock.setReferenceCounted(false);
connectionStatusLock = new ReentrantLock();
} }
@Override @Override
@@ -220,11 +204,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream()); Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream()); Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()) { while (stdout.hasNextLine() || stderr.hasNextLine()){
if (stdout.hasNextLine()) { if(stdout.hasNextLine()) {
LOG.info(stdout.nextLine()); LOG.info(stdout.nextLine());
} }
if (stderr.hasNextLine()) { if(stderr.hasNextLine()){
LOG.info(stderr.nextLine()); LOG.info(stderr.nextLine());
} }
} }
@@ -273,11 +257,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
// Register to receive network status events // Register to receive network status events
networkStateReceiver = new NetworkStateReceiver(); networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
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);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
// Bind a server socket to receive incoming hidden service connections // Bind a server socket to receive incoming hidden service connections
bind(); bind();
@@ -390,45 +370,57 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void sendDevReports() { private void sendDevReports() {
ioExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
// TODO: Trigger this with a TransportEnabledEvent @Override
File reportDir = AndroidUtils.getReportDir(appContext); public void run() {
reporter.sendReports(reportDir); // TODO: Trigger this with a TransportEnabledEvent
File reportDir = AndroidUtils.getReportDir(appContext);
reporter.sendReports(reportDir);
}
}); });
} }
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
// If there's already a port number stored in config, reuse it @Override
String portString = callback.getSettings().get(PREF_TOR_PORT); public void run() {
int port; // If there's already a port number stored in config, reuse it
if (StringUtils.isNullOrEmpty(portString)) port = 0; String portString = callback.getSettings().get(PREF_TOR_PORT);
else port = Integer.parseInt(portString); int port;
// Bind a server socket to receive connections from Tor if (StringUtils.isNullOrEmpty(portString)) port = 0;
ServerSocket ss = null; else port = Integer.parseInt(portString);
try { // Bind a server socket to receive connections from Tor
ss = new ServerSocket(); ServerSocket ss = null;
ss.bind(new InetSocketAddress("127.0.0.1", port)); try {
} catch (IOException e) { ss = new ServerSocket();
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); ss.bind(new InetSocketAddress("127.0.0.1", port));
tryToClose(ss); } catch (IOException e) {
return; 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) { private void connectAndCallBack(final ContactId c,
ioExecutor.execute(() -> { final TransportProperties p) {
if (!isRunning()) return; ioExecutor.execute(new Runnable() {
DuplexTransportConnection d = createConnection(p); @Override
if (d != null) { public void run() {
backoff.reset(); if (!isRunning()) return;
callback.outgoingConnectionCreated(c, d); DuplexTransportConnection d = createConnection(p);
if (d != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, d);
}
} }
}); });
} }
@@ -614,7 +610,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor, long timeout) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@@ -638,8 +634,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void orConnStatus(String status, String orName) { public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status); if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CLOSED") || status.equals("FAILED"))
updateConnectionStatus(); // Check whether we've lost connectivity
} }
@Override @Override
@@ -679,7 +673,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public void onEvent(int event, @Nullable String path) { public void onEvent(int event, String path) {
stopWatching(); stopWatching();
latch.countDown(); latch.countDown();
} }
@@ -697,75 +691,60 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
ioExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
if (!running) return; @Override
try { public void run() {
connectionStatusLock.lock(); if (!running) return;
updateConnectionStatusLocked();
} finally { Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
connectionStatusLock.unlock(); 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 { private class NetworkStateReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context ctx, Intent i) { public void onReceive(Context ctx, Intent i) {
if (!running) return; if (!running) return;
String action = i.getAction(); if (CONNECTIVITY_ACTION.equals(i.getAction())) {
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action); LOG.info("Detected connectivity change");
updateConnectionStatus(); updateConnectionStatus();
if (ACTION_SCREEN_ON.equals(action)
|| ACTION_SCREEN_OFF.equals(action)) {
scheduleConnectionStatusUpdate();
} }
} }
} }

View File

@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -37,7 +36,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final DevReporter reporter; private final DevReporter reporter;
@@ -45,13 +43,11 @@ public class TorPluginFactory implements DuplexPluginFactory {
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public TorPluginFactory(Executor ioExecutor, public TorPluginFactory(Executor ioExecutor, Context appContext,
ScheduledExecutorService scheduler, Context appContext,
LocationUtils locationUtils, DevReporter reporter, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus, SocketFactory torSocketFactory, EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory) { BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.reporter = reporter; this.reporter = reporter;
@@ -93,9 +89,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext, TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
locationUtils, reporter, torSocketFactory, backoff, callback, reporter, torSocketFactory, backoff, callback, architecture,
architecture, MAX_LATENCY, MAX_IDLE_TIME); MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -27,11 +27,14 @@ class AndroidExecutorImpl implements AndroidExecutor {
@Inject @Inject
AndroidExecutorImpl(Application app) { AndroidExecutorImpl(Application app) {
uiHandler = new Handler(app.getApplicationContext().getMainLooper()); uiHandler = new Handler(app.getApplicationContext().getMainLooper());
loop = () -> { loop = new Runnable() {
Looper.prepare(); @Override
backgroundHandler = new Handler(); public void run() {
startLatch.countDown(); Looper.prepare();
Looper.loop(); backgroundHandler = new Handler();
startLatch.countDown();
Looper.loop();
}
}; };
} }

View File

@@ -1,39 +1,30 @@
apply plugin: 'java-library' apply plugin: 'java'
sourceCompatibility = 1.8 sourceCompatibility = 1.6
targetCompatibility = 1.8 targetCompatibility = 1.6
apply plugin: 'witness' apply plugin: 'witness'
dependencies { dependencies {
implementation "com.google.dagger:dagger:2.0.2" compile "com.google.dagger:dagger:2.0.2"
implementation 'com.google.code.findbugs:jsr305:3.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' testCompile 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2" testCompile "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2" testCompile "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2" testCompile "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3" testCompile "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3" testCompile "org.hamcrest:hamcrest-core:1.3"
} }
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', 'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9', 'com.google.code.findbugs:jsr305:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'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',
] ]
} }
@@ -48,8 +39,3 @@ task jarTest(type: Jar, dependsOn: testClasses) {
artifacts { artifacts {
testOutput jarTest 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)
}

View File

@@ -23,7 +23,7 @@ public class BdfMessageContext {
} }
public BdfMessageContext(BdfDictionary dictionary) { public BdfMessageContext(BdfDictionary dictionary) {
this(dictionary, Collections.emptyList()); this(dictionary, Collections.<MessageId>emptyList());
} }
public BdfDictionary getDictionary() { public BdfDictionary getDictionary() {

View File

@@ -4,14 +4,11 @@ import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.concurrent.ConcurrentSkipListMap;
import java.util.TreeMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe public class BdfDictionary extends ConcurrentSkipListMap<String, Object> {
public class BdfDictionary extends TreeMap<String, Object> {
public static final Object NULL_VALUE = new Object(); public static final Object NULL_VALUE = new Object();

View File

@@ -3,17 +3,15 @@ package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Vector;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
@NotThreadSafe public class BdfList extends Vector<Object> {
public class BdfList extends ArrayList<Object> {
/** /**
* Factory method for constructing lists inline. * Factory method for constructing lists inline.

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -37,13 +37,8 @@ public interface DatabaseComponent {
/** /**
* Opens the database and returns true if the database already existed. * 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. * Waits for any open transactions to finish and closes the database.
@@ -127,9 +122,8 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Deletes the message with the given ID. Unlike * Deletes the message with the given ID. The message ID and any other
* {@link #removeMessage(Transaction, MessageId)}, the message ID and any * associated data are not deleted.
* other associated data are not deleted.
*/ */
void deleteMessage(Transaction txn, MessageId m) throws DbException; void deleteMessage(Transaction txn, MessageId m) throws DbException;
@@ -378,16 +372,6 @@ public interface DatabaseComponent {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; 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. * Returns all settings in the given namespace.
* <p/> * <p/>
@@ -468,11 +452,6 @@ public interface DatabaseComponent {
*/ */
void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException; 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. * Removes a transport (and all associated state) from the database.
*/ */

View File

@@ -1,11 +1,11 @@
package org.briarproject.bramble.api.db; 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 @ThreadSafe
public class Metadata extends TreeMap<String, byte[]> { public class Metadata extends Hashtable<String, byte[]> {
/** /**
* Special value to indicate that a key is being removed. * Special value to indicate that a key is being removed.

View File

@@ -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();
}

View File

@@ -45,7 +45,7 @@ public class Transaction {
* committed. * committed.
*/ */
public void attach(Event e) { public void attach(Event e) {
if (events == null) events = new ArrayList<>(); if (events == null) events = new ArrayList<Event>();
events.add(e); events.add(e);
} }

View File

@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
import org.briarproject.bramble.api.data.BdfList; 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. * 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. * Starts listening for incoming connections, and returns a Callable that
* * will return a KeyAgreementConnection when an incoming connection is
* @throws IOException if an error occurs or {@link #close()} is called. * received.
*/ */
public abstract KeyAgreementConnection accept() throws IOException; public abstract Callable<KeyAgreementConnection> listen();
/** /**
* Closes the underlying server socket. * Closes the underlying server socket.

View File

@@ -21,25 +21,7 @@ public interface LifecycleManager {
* The result of calling {@link #startServices(String)}. * The result of calling {@link #startServices(String)}.
*/ */
enum StartResult { enum StartResult {
ALREADY_RUNNING, ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
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();
}
} }
/** /**
@@ -89,10 +71,4 @@ public interface LifecycleManager {
* the {@link DatabaseComponent} to be closed before returning. * the {@link DatabaseComponent} to be closed before returning.
*/ */
void waitForShutdown() throws InterruptedException; void waitForShutdown() throws InterruptedException;
/**
* Returns the current state of the lifecycle.
*/
LifecycleState getLifecycleState();
} }

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble.api.plugin;
public enum BluetoothEnableDisableReason {
COMMUNICATION,
ADD_CONTACT
}

View File

@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
/** /**
* Attempts to connect to the remote peer specified in the given descriptor. * 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 @Nullable
DuplexTransportConnection createKeyAgreementConnection( DuplexTransportConnection createKeyAgreementConnection(
byte[] remoteCommitment, BdfList descriptor); byte[] remoteCommitment, BdfList descriptor, long timeout);
} }

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.bramble.api.plugin.event; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -11,5 +11,8 @@ import javax.annotation.concurrent.Immutable;
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class DisableBluetoothEvent extends Event { public class DisableBluetoothEvent extends BluetoothEvent {
public DisableBluetoothEvent(BluetoothEnableDisableReason reason) {
super(reason);
}
} }

View File

@@ -2,13 +2,17 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothEnableDisableReason;
import javax.annotation.concurrent.Immutable; 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 @Immutable
@NotNullByDefault @NotNullByDefault
public class EnableBluetoothEvent extends Event { public class EnableBluetoothEvent extends BluetoothEvent {
public EnableBluetoothEvent(BluetoothEnableDisableReason reason){
super(reason);
}
} }

View File

@@ -33,7 +33,7 @@ public interface TransportPropertyManager {
/** /**
* Returns the local transport properties for all transports. * Returns the local transport properties for all transports.
* <br/> * <br/>
* TODO: Transaction can be read-only when code is simplified * Read-Only
*/ */
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn) Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
throws DbException; throws DbException;

View File

@@ -22,7 +22,7 @@ public class MessageContext {
} }
public MessageContext(Metadata metadata) { public MessageContext(Metadata metadata) {
this(metadata, Collections.emptyList()); this(metadata, Collections.<MessageId>emptyList());
} }
public Metadata getMetadata() { public Metadata getMetadata() {

View File

@@ -126,10 +126,6 @@ public class StringUtils {
return toUtf8(s).length > maxLength; return toUtf8(s).length > maxLength;
} }
public static boolean isValidMac(String mac) {
return MAC.matcher(mac).matches();
}
public static byte[] macToBytes(String mac) { public static byte[] macToBytes(String mac) {
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException(); if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
return fromHexString(mac.replaceAll(":", "")); return fromHexString(mac.replaceAll(":", ""));

View File

@@ -3,7 +3,8 @@ package org.briarproject.bramble.test;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.junit.After; import org.junit.After;
public abstract class BrambleMockTestCase extends BrambleTestCase { public abstract class BrambleMockTestCase extends
BrambleTestCase {
protected final Mockery context = new Mockery(); protected final Mockery context = new Mockery();

View File

@@ -8,9 +8,12 @@ public abstract class BrambleTestCase {
public BrambleTestCase() { public BrambleTestCase() {
// Ensure exceptions thrown on worker threads cause tests to fail // Ensure exceptions thrown on worker threads cause tests to fail
UncaughtExceptionHandler fail = (thread, throwable) -> { UncaughtExceptionHandler fail = new UncaughtExceptionHandler() {
throwable.printStackTrace(); @Override
fail(); public void uncaughtException(Thread thread, Throwable throwable) {
throwable.printStackTrace();
fail();
}
}; };
Thread.setDefaultUncaughtExceptionHandler(fail); Thread.setDefaultUncaughtExceptionHandler(fail);
} }

View File

@@ -2,27 +2,12 @@ package org.briarproject.bramble.test;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.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 org.briarproject.bramble.util.IoUtils;
import java.io.File; import java.io.File;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; 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 { public class TestUtils {
private static final AtomicInteger nextTestDir = private static final AtomicInteger nextTestDir =
@@ -53,50 +38,4 @@ public class TestUtils {
return new SecretKey(getRandomBytes(SecretKey.LENGTH)); 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);
}
} }

View File

@@ -1,54 +1,28 @@
apply plugin: 'java-library' plugins {
sourceCompatibility = 1.8 id 'java'
targetCompatibility = 1.8 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' apply plugin: 'witness'
dependencies { dependencies {
implementation project(path: ':bramble-api', configuration: 'default') compile project(path: ':bramble-api', configuration: 'default')
implementation 'com.madgag.spongycastle:core:1.58.0.0' compile '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 compile 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4' compile 'org.bitlet:weupnp:0.1.4'
apt 'com.google.dagger:dagger-compiler:2.0.2' testCompile project(path: ':bramble-api', configuration: 'testOutput')
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'
} }
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', 'com.madgag.spongycastle:core:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.h2database:h2:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3', 'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'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',
] ]
} }
@@ -63,8 +37,3 @@ task jarTest(type: Jar, dependsOn: testClasses) {
artifacts { artifacts {
testOutput jarTest 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)
}

View File

@@ -24,7 +24,7 @@ public class PoliteExecutor implements Executor {
private final Object lock = new Object(); private final Object lock = new Object();
@GuardedBy("lock") @GuardedBy("lock")
private final Queue<Runnable> queue = new LinkedList<>(); private final Queue<Runnable> queue = new LinkedList<Runnable>();
private final Executor delegate; private final Executor delegate;
private final int maxConcurrentTasks; private final int maxConcurrentTasks;
private final Logger log; private final Logger log;
@@ -48,17 +48,20 @@ public class PoliteExecutor implements Executor {
} }
@Override @Override
public void execute(Runnable r) { public void execute(final Runnable r) {
long submitted = System.currentTimeMillis(); final long submitted = System.currentTimeMillis();
Runnable wrapped = () -> { Runnable wrapped = new Runnable() {
if (log.isLoggable(LOG_LEVEL)) { @Override
long queued = System.currentTimeMillis() - submitted; public void run() {
log.log(LOG_LEVEL, "Queue time " + queued + " ms"); if (log.isLoggable(LOG_LEVEL)) {
} long queued = System.currentTimeMillis() - submitted;
try { log.log(LOG_LEVEL, "Queue time " + queued + " ms");
r.run(); }
} finally { try {
scheduleNext(); r.run();
} finally {
scheduleNext();
}
} }
}; };
synchronized (lock) { synchronized (lock) {

View File

@@ -28,16 +28,19 @@ public class TimeLoggingExecutor extends ThreadPoolExecutor {
} }
@Override @Override
public void execute(Runnable r) { public void execute(final Runnable r) {
if (log.isLoggable(LOG_LEVEL)) { if (log.isLoggable(LOG_LEVEL)) {
long submitted = System.currentTimeMillis(); final long submitted = System.currentTimeMillis();
super.execute(() -> { super.execute(new Runnable() {
long started = System.currentTimeMillis(); @Override
long queued = started - submitted; public void run() {
log.log(LOG_LEVEL, "Queue time " + queued + " ms"); long started = System.currentTimeMillis();
r.run(); long queued = started - submitted;
long executing = System.currentTimeMillis() - started; log.log(LOG_LEVEL, "Queue time " + queued + " ms");
log.log(LOG_LEVEL, "Execution time " + executing + " ms"); r.run();
long executing = System.currentTimeMillis() - started;
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
}
}); });
} else { } else {
super.execute(r); super.execute(r);

View File

@@ -201,7 +201,8 @@ class ClientHelperImpl implements ClientHelper {
public Map<MessageId, BdfDictionary> getMessageMetadataAsDictionary( public Map<MessageId, BdfDictionary> getMessageMetadataAsDictionary(
Transaction txn, GroupId g) throws DbException, FormatException { Transaction txn, GroupId g) throws DbException, FormatException {
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g); 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()) for (Entry<MessageId, Metadata> e : raw.entrySet())
parsed.put(e.getKey(), metadataParser.parse(e.getValue())); parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
return parsed; return parsed;
@@ -228,7 +229,8 @@ class ClientHelperImpl implements ClientHelper {
FormatException { FormatException {
Metadata metadata = metadataEncoder.encode(query); Metadata metadata = metadataEncoder.encode(query);
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g, metadata); 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()) for (Entry<MessageId, Metadata> e : raw.entrySet())
parsed.put(e.getKey(), metadataParser.parse(e.getValue())); parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
return parsed; return parsed;

View File

@@ -184,7 +184,12 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
// Close the outgoing stream and expect EOF on the incoming stream // Close the outgoing stream and expect EOF on the incoming stream
w.close(); w.close();
if (!r.eof()) LOG.warning("Unexpected data at end of connection"); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
listener.contactExchangeFailed(); listener.contactExchangeFailed();
tryToClose(conn, true); tryToClose(conn, true);
@@ -271,7 +276,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private Map<TransportId, TransportProperties> receiveTransportProperties( private Map<TransportId, TransportProperties> receiveTransportProperties(
BdfReader r) throws IOException { BdfReader r) throws IOException {
Map<TransportId, TransportProperties> remote = new HashMap<>(); Map<TransportId, TransportProperties> remote =
new HashMap<TransportId, TransportProperties>();
r.readListStart(); r.readListStart();
while (!r.hasListEnd()) { while (!r.hasListEnd()) {
r.readListStart(); r.readListStart();

View File

@@ -34,8 +34,8 @@ class ContactManagerImpl implements ContactManager {
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) { ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
this.db = db; this.db = db;
this.keyManager = keyManager; this.keyManager = keyManager;
addHooks = new CopyOnWriteArrayList<>(); addHooks = new CopyOnWriteArrayList<AddContactHook>();
removeHooks = new CopyOnWriteArrayList<>(); removeHooks = new CopyOnWriteArrayList<RemoveContactHook>();
} }
@Override @Override
@@ -125,7 +125,7 @@ class ContactManagerImpl implements ContactManager {
} finally { } finally {
db.endTransaction(txn); 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); for (Contact c : contacts) if (c.isActive()) active.add(c);
return active; return active;
} }

View File

@@ -602,8 +602,8 @@ class CryptoComponentImpl implements CryptoComponent {
// Package access for testing // Package access for testing
int chooseIterationCount(int targetMillis) { int chooseIterationCount(int targetMillis) {
List<Long> quickSamples = new ArrayList<>(PBKDF_SAMPLES); List<Long> quickSamples = new ArrayList<Long>(PBKDF_SAMPLES);
List<Long> slowSamples = new ArrayList<>(PBKDF_SAMPLES); List<Long> slowSamples = new ArrayList<Long>(PBKDF_SAMPLES);
long iterationNanos = 0, initNanos = 0; long iterationNanos = 0, initNanos = 0;
while (iterationNanos <= 0 || initNanos <= 0) { while (iterationNanos <= 0 || initNanos <= 0) {
// Sample the running time with one iteration and two iterations // Sample the running time with one iteration and two iterations

View File

@@ -48,7 +48,7 @@ public class CryptoModule {
public CryptoModule() { public CryptoModule() {
// Use an unbounded queue // Use an unbounded queue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
// Discard tasks that are submitted during shutdown // Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy = RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy(); new ThreadPoolExecutor.DiscardPolicy();

View File

@@ -16,7 +16,7 @@ class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
@Override @Override
public float estimateStrength(String password) { public float estimateStrength(String password) {
HashSet<Character> unique = new HashSet<>(); HashSet<Character> unique = new HashSet<Character>();
int length = password.length(); int length = password.length();
for (int i = 0; i < length; i++) unique.add(password.charAt(i)); for (int i = 0; i < length; i++) unique.add(password.charAt(i));
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS); return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);

View File

@@ -2,11 +2,8 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -40,13 +37,8 @@ interface Database<T> {
/** /**
* Opens the database and returns true if the database already existed. * 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 * Prevents new transactions from starting, waits for all current
@@ -457,16 +449,6 @@ interface Database<T> {
Collection<MessageId> getMessagesToShare(T txn, ClientId c) Collection<MessageId> getMessagesToShare(T txn, ClientId c)
throws DbException; 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 * Returns the message with the given ID, in serialised form, or null if
* the message has been deleted. * the message has been deleted.

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; 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.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
@@ -91,6 +90,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
private final ReentrantReadWriteLock lock = private final ReentrantReadWriteLock lock =
new ReentrantReadWriteLock(true); new ReentrantReadWriteLock(true);
private volatile int shutdownHandle = -1;
@Inject @Inject
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus, DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
ShutdownManager shutdown) { ShutdownManager shutdown) {
@@ -101,22 +102,27 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean open(@Nullable MigrationListener listener) public boolean open() throws DbException {
throws DbException { Runnable shutdownHook = new Runnable() {
boolean reopened = db.open(listener); @Override
shutdown.addShutdownHook(() -> { public void run() {
try { try {
close(); close();
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); };
boolean reopened = db.open();
shutdownHandle = shutdown.addShutdownHook(shutdownHook);
return reopened; return reopened;
} }
@Override @Override
public void close() throws DbException { public void close() throws DbException {
if (closed.getAndSet(true)) return; if (closed.getAndSet(true)) return;
shutdown.removeShutdownHook(shutdownHandle);
db.close(); db.close();
} }
@@ -135,7 +141,11 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
try { try {
return new Transaction(db.startTransaction(), readOnly); 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(); if (readOnly) lock.readLock().unlock();
else lock.writeLock().unlock(); else lock.writeLock().unlock();
throw e; throw e;
@@ -321,7 +331,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = db.getMessagesToSend(txn, c, maxLength); 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) { for (MessageId m : ids) {
messages.add(db.getRawMessage(txn, m)); messages.add(db.getRawMessage(txn, m));
db.updateExpiryTime(txn, c, m, maxLatency); db.updateExpiryTime(txn, c, m, maxLatency);
@@ -371,7 +381,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> ids = db.getRequestedMessagesToSend(txn, c, Collection<MessageId> ids = db.getRequestedMessagesToSend(txn, c,
maxLength); maxLength);
List<byte[]> messages = new ArrayList<>(ids.size()); List<byte[]> messages = new ArrayList<byte[]>(ids.size());
for (MessageId m : ids) { for (MessageId m : ids) {
messages.add(db.getRawMessage(txn, m)); messages.add(db.getRawMessage(txn, m));
db.updateExpiryTime(txn, c, m, maxLatency); db.updateExpiryTime(txn, c, m, maxLatency);
@@ -581,13 +591,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageDependents(txn, m); 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 @Override
public Settings getSettings(Transaction transaction, String namespace) public Settings getSettings(Transaction transaction, String namespace)
throws DbException { throws DbException {
@@ -658,7 +661,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsContact(txn, c)) if (!db.containsContact(txn, c))
throw new NoSuchContactException(); throw new NoSuchContactException();
Collection<MessageId> acked = new ArrayList<>(); Collection<MessageId> acked = new ArrayList<MessageId>();
for (MessageId m : a.getMessageIds()) { for (MessageId m : a.getMessageIds()) {
if (db.containsVisibleMessage(txn, c, m)) { if (db.containsVisibleMessage(txn, c, m)) {
db.raiseSeenFlag(txn, c, m); db.raiseSeenFlag(txn, c, m);
@@ -767,16 +770,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
transaction.attach(new LocalAuthorRemovedEvent(a)); 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 @Override
public void removeTransport(Transaction transaction, TransportId t) public void removeTransport(Transaction transaction, TransportId t)
throws DbException { throws DbException {
@@ -893,7 +886,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
Map<ContactId, TransportKeys> keys) throws DbException { Map<ContactId, TransportKeys> keys) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); 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()) { for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
ContactId c = e.getKey(); ContactId c = e.getKey();
TransportKeys k = e.getValue(); TransportKeys k = e.getValue();

View File

@@ -23,4 +23,10 @@ interface DatabaseConstants {
*/ */
String SCHEMA_VERSION_KEY = "schemaVersion"; 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";
} }

View File

@@ -32,7 +32,7 @@ public class DatabaseExecutorModule {
public DatabaseExecutorModule() { public DatabaseExecutorModule() {
// Use an unbounded queue // Use an unbounded queue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
// Discard tasks that are submitted during shutdown // Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy = RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy(); new ThreadPoolExecutor.DiscardPolicy();

View File

@@ -26,7 +26,7 @@ public class DatabaseModule {
@Singleton @Singleton
DatabaseComponent provideDatabaseComponent(Database<Connection> db, DatabaseComponent provideDatabaseComponent(Database<Connection> db,
EventBus eventBus, ShutdownManager shutdown) { EventBus eventBus, ShutdownManager shutdown) {
return new DatabaseComponentImpl<>(db, Connection.class, eventBus, return new DatabaseComponentImpl<Connection>(db, Connection.class,
shutdown); eventBus, shutdown);
} }
} }

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -14,7 +13,6 @@ import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@@ -42,11 +40,10 @@ class H2Database extends JdbcDatabase {
} }
@Override @Override
public boolean open(@Nullable MigrationListener listener) public boolean open() throws DbException {
throws DbException {
boolean reopen = config.databaseExists(); boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs(); if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen, listener); super.open("org.h2.Driver", reopen);
return reopen; return reopen;
} }

View File

@@ -3,12 +3,9 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey; 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.DbClosedException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -50,7 +47,6 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
@@ -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.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; 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.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.DatabaseConstants.SCHEMA_VERSION_KEY;
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry; import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
@@ -71,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
@NotNullByDefault @NotNullByDefault
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing private static final int SCHEMA_VERSION = 30;
static final int CODE_SCHEMA_VERSION = 31; private static final int MIN_SCHEMA_VERSION = 30;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"
@@ -151,16 +148,11 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_MESSAGE_METADATA = private static final String CREATE_MESSAGE_METADATA =
"CREATE TABLE messageMetadata" "CREATE TABLE messageMetadata"
+ " (messageId HASH NOT NULL," + " (messageId HASH NOT NULL,"
+ " groupId HASH NOT NULL," // Denormalised
+ " state INT NOT NULL," // Denormalised
+ " key VARCHAR NOT NULL," + " key VARCHAR NOT NULL,"
+ " value BINARY NOT NULL," + " value BINARY NOT NULL,"
+ " PRIMARY KEY (messageId, key)," + " PRIMARY KEY (messageId, key),"
+ " FOREIGN KEY (messageId)" + " FOREIGN KEY (messageId)"
+ " REFERENCES messages (messageId)" + " REFERENCES messages (messageId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
private static final String CREATE_MESSAGE_DEPENDENCIES = private static final String CREATE_MESSAGE_DEPENDENCIES =
@@ -240,18 +232,6 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " REFERENCES transports (transportId)" + " REFERENCES transports (transportId)"
+ " ON DELETE CASCADE)"; + " 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 = private static final Logger LOG =
Logger.getLogger(JdbcDatabase.class.getName()); Logger.getLogger(JdbcDatabase.class.getName());
@@ -259,8 +239,8 @@ abstract class JdbcDatabase implements Database<Connection> {
private final String hashType, binaryType, counterType, secretType; private final String hashType, binaryType, counterType, secretType;
private final Clock clock; private final Clock clock;
// Locking: connectionsLock private final LinkedList<Connection> connections =
private final LinkedList<Connection> connections = new LinkedList<>(); new LinkedList<Connection>(); // Locking: connectionsLock
private int openConnections = 0; // Locking: connectionsLock private int openConnections = 0; // Locking: connectionsLock
private boolean closed = false; // Locking: connectionsLock private boolean closed = false; // Locking: connectionsLock
@@ -280,24 +260,22 @@ abstract class JdbcDatabase implements Database<Connection> {
this.clock = clock; this.clock = clock;
} }
protected void open(String driverClass, boolean reopen, protected void open(String driverClass, boolean reopen) throws DbException {
@Nullable MigrationListener listener) throws DbException {
// Load the JDBC driver // Load the JDBC driver
try { try {
Class.forName(driverClass); Class.forName(driverClass);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new DbException(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(); Connection txn = startTransaction();
try { try {
if (reopen) { if (reopen) {
checkSchemaVersion(txn, listener); if (!checkSchemaVersion(txn)) throw new DbException();
} else { } else {
createTables(txn); createTables(txn);
storeSchemaVersion(txn, CODE_SCHEMA_VERSION); storeSchemaVersion(txn);
} }
createIndexes(txn);
commitTransaction(txn); commitTransaction(txn);
} catch (DbException e) { } catch (DbException e) {
abortTransaction(txn); abortTransaction(txn);
@@ -305,51 +283,19 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
/** private boolean checkSchemaVersion(Connection txn) throws DbException {
* 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 {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE); Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1); int schemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
if (dataSchemaVersion == -1) throw new DbException(); if (schemaVersion == SCHEMA_VERSION) return true;
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return; if (schemaVersion < MIN_SCHEMA_VERSION) return false;
if (CODE_SCHEMA_VERSION < dataSchemaVersion) int minSchemaVersion = s.getInt(MIN_SCHEMA_VERSION_KEY, -1);
throw new DataTooNewException(); return SCHEMA_VERSION >= minSchemaVersion;
// 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();
} }
// Package access for testing private void storeSchemaVersion(Connection txn) throws DbException {
List<Migration<Connection>> getMigrations() {
return Collections.singletonList(new Migration30_31());
}
private void storeSchemaVersion(Connection txn, int version)
throws DbException {
Settings s = new Settings(); 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); 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) { private String insertTypeNames(String s) {
s = s.replaceAll("HASH", hashType); s = s.replaceAll("HASH", hashType);
s = s.replaceAll("BINARY", binaryType); s = s.replaceAll("BINARY", binaryType);
@@ -526,8 +458,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
// Create a contact row // Create a contact row
String sql = "INSERT INTO contacts" String sql = "INSERT INTO contacts"
+ " (authorId, name, publicKey, localAuthorId," + " (authorId, name, publicKey, localAuthorId, verified, active)"
+ " verified, active)"
+ " VALUES (?, ?, ?, ?, ?, ?)"; + " VALUES (?, ?, ?, ?, ?, ?)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, remote.getId().getBytes()); ps.setBytes(1, remote.getId().getBytes());
@@ -1062,7 +993,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " FROM contacts"; + " FROM contacts";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<Contact> contacts = new ArrayList<>(); List<Contact> contacts = new ArrayList<Contact>();
while (rs.next()) { while (rs.next()) {
ContactId contactId = new ContactId(rs.getInt(1)); ContactId contactId = new ContactId(rs.getInt(1));
AuthorId authorId = new AuthorId(rs.getBytes(2)); AuthorId authorId = new AuthorId(rs.getBytes(2));
@@ -1096,7 +1027,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, local.getBytes()); ps.setBytes(1, local.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<ContactId> ids = new ArrayList<>(); List<ContactId> ids = new ArrayList<ContactId>();
while (rs.next()) ids.add(new ContactId(rs.getInt(1))); while (rs.next()) ids.add(new ContactId(rs.getInt(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1121,7 +1052,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, remote.getBytes()); ps.setBytes(1, remote.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<Contact> contacts = new ArrayList<>(); List<Contact> contacts = new ArrayList<Contact>();
while (rs.next()) { while (rs.next()) {
ContactId c = new ContactId(rs.getInt(1)); ContactId c = new ContactId(rs.getInt(1));
String name = rs.getString(2); String name = rs.getString(2);
@@ -1177,7 +1108,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, c.getString()); ps.setString(1, c.getString());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<Group> groups = new ArrayList<>(); List<Group> groups = new ArrayList<Group>();
while (rs.next()) { while (rs.next()) {
GroupId id = new GroupId(rs.getBytes(1)); GroupId id = new GroupId(rs.getBytes(1));
byte[] descriptor = rs.getBytes(2); byte[] descriptor = rs.getBytes(2);
@@ -1230,7 +1161,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<ContactId> visible = new ArrayList<>(); List<ContactId> visible = new ArrayList<ContactId>();
while (rs.next()) visible.add(new ContactId(rs.getInt(1))); while (rs.next()) visible.add(new ContactId(rs.getInt(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1282,7 +1213,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " FROM localAuthors"; + " FROM localAuthors";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<LocalAuthor> authors = new ArrayList<>(); List<LocalAuthor> authors = new ArrayList<LocalAuthor>();
while (rs.next()) { while (rs.next()) {
AuthorId authorId = new AuthorId(rs.getBytes(1)); AuthorId authorId = new AuthorId(rs.getBytes(1));
String name = rs.getString(2); String name = rs.getString(2);
@@ -1312,7 +1243,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1335,7 +1266,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, state.getValue()); ps.setInt(1, state.getValue());
ps.setBytes(2, g.getBytes()); ps.setBytes(2, g.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1357,17 +1288,20 @@ abstract class JdbcDatabase implements Database<Connection> {
try { try {
// Retrieve the message IDs for each query term and intersect // Retrieve the message IDs for each query term and intersect
Set<MessageId> intersection = null; Set<MessageId> intersection = null;
String sql = "SELECT messageId FROM messageMetadata" String sql = "SELECT m.messageId"
+ " WHERE groupId = ? AND state = ?" + " FROM messages AS m"
+ " JOIN messageMetadata AS md"
+ " ON m.messageId = md.messageId"
+ " WHERE state = ? AND groupId = ?"
+ " AND key = ? AND value = ?"; + " AND key = ? AND value = ?";
for (Entry<String, byte[]> e : query.entrySet()) { for (Entry<String, byte[]> e : query.entrySet()) {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setInt(1, DELIVERED.getValue());
ps.setInt(2, DELIVERED.getValue()); ps.setBytes(2, g.getBytes());
ps.setString(3, e.getKey()); ps.setString(3, e.getKey());
ps.setBytes(4, e.getValue()); ps.setBytes(4, e.getValue());
rs = ps.executeQuery(); rs = ps.executeQuery();
Set<MessageId> ids = new HashSet<>(); Set<MessageId> ids = new HashSet<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1391,20 +1325,25 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT messageId, key, value" String sql = "SELECT m.messageId, key, value"
+ " FROM messageMetadata" + " FROM messages AS m"
+ " WHERE groupId = ? AND state = ?"; + " JOIN messageMetadata AS md"
+ " ON m.messageId = md.messageId"
+ " WHERE state = ? AND groupId = ?"
+ " ORDER BY m.messageId";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setInt(1, DELIVERED.getValue());
ps.setInt(2, DELIVERED.getValue()); ps.setBytes(2, g.getBytes());
rs = ps.executeQuery(); 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()) { while (rs.next()) {
MessageId messageId = new MessageId(rs.getBytes(1)); MessageId messageId = new MessageId(rs.getBytes(1));
Metadata metadata = all.get(messageId); if (lastMessageId == null || !messageId.equals(lastMessageId)) {
if (metadata == null) {
metadata = new Metadata(); metadata = new Metadata();
all.put(messageId, metadata); all.put(messageId, metadata);
lastMessageId = messageId;
} }
metadata.put(rs.getString(2), rs.getBytes(3)); 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); Collection<MessageId> matches = getMessageIds(txn, g, query);
if (matches.isEmpty()) return Collections.emptyMap(); if (matches.isEmpty()) return Collections.emptyMap();
// Retrieve the metadata for each match // 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)); for (MessageId m : matches) all.put(m, getMessageMetadata(txn, m));
return all; return all;
} }
@@ -1459,8 +1399,10 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT key, value FROM messageMetadata" String sql = "SELECT key, value FROM messageMetadata AS md"
+ " WHERE state = ? AND messageId = ?"; + " JOIN messages AS m"
+ " ON m.messageId = md.messageId"
+ " WHERE m.state = ? AND md.messageId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, DELIVERED.getValue()); ps.setInt(1, DELIVERED.getValue());
ps.setBytes(2, m.getBytes()); ps.setBytes(2, m.getBytes());
@@ -1483,9 +1425,11 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT key, value FROM messageMetadata" String sql = "SELECT key, value FROM messageMetadata AS md"
+ " WHERE (state = ? OR state = ?)" + " JOIN messages AS m"
+ " AND messageId = ?"; + " ON m.messageId = md.messageId"
+ " WHERE (m.state = ? OR m.state = ?)"
+ " AND md.messageId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, DELIVERED.getValue()); ps.setInt(1, DELIVERED.getValue());
ps.setInt(2, PENDING.getValue()); ps.setInt(2, PENDING.getValue());
@@ -1519,7 +1463,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageStatus> statuses = new ArrayList<>(); List<MessageStatus> statuses = new ArrayList<MessageStatus>();
while (rs.next()) { while (rs.next()) {
MessageId messageId = new MessageId(rs.getBytes(1)); MessageId messageId = new MessageId(rs.getBytes(1));
boolean sent = rs.getBoolean(2); boolean sent = rs.getBoolean(2);
@@ -1578,7 +1522,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<MessageId, State> dependencies = new HashMap<>(); Map<MessageId, State> dependencies = new HashMap<MessageId, State>();
while (rs.next()) { while (rs.next()) {
MessageId dependency = new MessageId(rs.getBytes(1)); MessageId dependency = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2)); State state = State.fromValue(rs.getInt(2));
@@ -1616,7 +1560,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<MessageId, State> dependents = new HashMap<>(); Map<MessageId, State> dependents = new HashMap<MessageId, State>();
while (rs.next()) { while (rs.next()) {
MessageId dependent = new MessageId(rs.getBytes(1)); MessageId dependent = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2)); State state = State.fromValue(rs.getInt(2));
@@ -1668,7 +1612,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, maxMessages); ps.setInt(2, maxMessages);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1704,7 +1648,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setLong(3, now); ps.setLong(3, now);
ps.setInt(4, maxMessages); ps.setInt(4, maxMessages);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1729,7 +1673,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setInt(2, maxMessages); ps.setInt(2, maxMessages);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1764,7 +1708,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now); ps.setLong(3, now);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
int total = 0; int total = 0;
while (rs.next()) { while (rs.next()) {
int length = rs.getInt(1); int length = rs.getInt(1);
@@ -1806,7 +1750,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(1, state.getValue()); ps.setInt(1, state.getValue());
ps.setString(2, c.getString()); ps.setString(2, c.getString());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.close(); ps.close();
@@ -1836,7 +1780,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, c.getString()); ps.setString(1, c.getString());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
rs.close(); rs.close();
ps.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 @Override
@Nullable @Nullable
public byte[] getRawMessage(Connection txn, MessageId m) public byte[] getRawMessage(Connection txn, MessageId m)
@@ -1930,7 +1839,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now); ps.setLong(3, now);
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<MessageId>();
int total = 0; int total = 0;
while (rs.next()) { while (rs.next()) {
int length = rs.getInt(1); int length = rs.getInt(1);
@@ -1984,7 +1893,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, t.getString()); ps.setString(1, t.getString());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<IncomingKeys> inKeys = new ArrayList<>(); List<IncomingKeys> inKeys = new ArrayList<IncomingKeys>();
while (rs.next()) { while (rs.next()) {
long rotationPeriod = rs.getLong(1); long rotationPeriod = rs.getLong(1);
SecretKey tagKey = new SecretKey(rs.getBytes(2)); SecretKey tagKey = new SecretKey(rs.getBytes(2));
@@ -2004,7 +1913,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, t.getString()); ps.setString(1, t.getString());
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<ContactId, TransportKeys> keys = new HashMap<>(); Map<ContactId, TransportKeys> keys =
new HashMap<ContactId, TransportKeys>();
for (int i = 0; rs.next(); i++) { for (int i = 0; rs.next(); i++) {
// There should be three times as many incoming keys // There should be three times as many incoming keys
if (inKeys.size() < (i + 1) * 3) throw new DbStateException(); if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
@@ -2094,7 +2004,7 @@ abstract class JdbcDatabase implements Database<Connection> {
int[] batchAffected = ps.executeBatch(); int[] batchAffected = ps.executeBatch();
if (batchAffected.length != requested.size()) if (batchAffected.length != requested.size())
throw new DbStateException(); throw new DbStateException();
for (int rows : batchAffected) { for (int rows: batchAffected) {
if (rows < 0) throw new DbStateException(); if (rows < 0) throw new DbStateException();
if (rows > 1) throw new DbStateException(); if (rows > 1) throw new DbStateException();
} }
@@ -2108,92 +2018,25 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta) public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
throws DbException { throws DbException {
PreparedStatement ps = null; mergeMetadata(txn, g.getBytes(), meta, "groupMetadata", "groupId");
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);
}
} }
@Override @Override
public void mergeMessageMetadata(Connection txn, MessageId m, public void mergeMessageMetadata(Connection txn, MessageId m, Metadata meta)
Metadata meta) throws DbException { throws DbException {
PreparedStatement ps = null; mergeMetadata(txn, m.getBytes(), meta, "messageMetadata", "messageId");
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);
}
} }
// Removes or updates any existing entries, returns any entries that private void mergeMetadata(Connection txn, byte[] id, Metadata meta,
// need to be added String tableName, String columnName) throws DbException {
private Map<String, byte[]> removeOrUpdateMetadata(Connection txn,
byte[] id, Metadata meta, String tableName, String columnName)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
// Determine which keys are being removed // Determine which keys are being removed
List<String> removed = new ArrayList<>(); List<String> removed = new ArrayList<String>();
Map<String, byte[]> notRemoved = new HashMap<>(); Map<String, byte[]> retained = new HashMap<String, byte[]>();
for (Entry<String, byte[]> e : meta.entrySet()) { for (Entry<String, byte[]> e : meta.entrySet()) {
if (e.getValue() == REMOVE) removed.add(e.getKey()); 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 // Delete any keys that are being removed
if (!removed.isEmpty()) { if (!removed.isEmpty()) {
@@ -2214,33 +2057,45 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
ps.close(); ps.close();
} }
if (notRemoved.isEmpty()) return Collections.emptyMap(); if (retained.isEmpty()) return;
// Update any keys that already exist // Update any keys that already exist
String sql = "UPDATE " + tableName + " SET value = ?" String sql = "UPDATE " + tableName + " SET value = ?"
+ " WHERE " + columnName + " = ? AND key = ?"; + " WHERE " + columnName + " = ? AND key = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(2, id); ps.setBytes(2, id);
for (Entry<String, byte[]> e : notRemoved.entrySet()) { for (Entry<String, byte[]> e : retained.entrySet()) {
ps.setBytes(1, e.getValue()); ps.setBytes(1, e.getValue());
ps.setString(3, e.getKey()); ps.setString(3, e.getKey());
ps.addBatch(); ps.addBatch();
} }
int[] batchAffected = ps.executeBatch(); int[] batchAffected = ps.executeBatch();
if (batchAffected.length != notRemoved.size()) if (batchAffected.length != retained.size())
throw new DbStateException(); throw new DbStateException();
for (int rows : batchAffected) { for (int rows : batchAffected) {
if (rows < 0) throw new DbStateException(); if (rows < 0) throw new DbStateException();
if (rows > 1) throw new DbStateException(); if (rows > 1) throw new DbStateException();
} }
ps.close(); // Insert any keys that don't already exist
// Are there any keys that don't already exist? sql = "INSERT INTO " + tableName
Map<String, byte[]> added = new HashMap<>(); + " (" + columnName + ", key, value)"
int updateIndex = 0; + " VALUES (?, ?, ?)";
for (Entry<String, byte[]> e : notRemoved.entrySet()) { ps = txn.prepareStatement(sql);
if (batchAffected[updateIndex++] == 0) ps.setBytes(1, id);
added.put(e.getKey(), e.getValue()); 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) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
@@ -2593,8 +2448,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public void setMessageShared(Connection txn, MessageId m) public void setMessageShared(Connection txn, MessageId m) throws DbException {
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "UPDATE messages SET shared = TRUE" String sql = "UPDATE messages SET shared = TRUE"
@@ -2622,14 +2476,6 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
// 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) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -15,7 +15,7 @@ import javax.annotation.concurrent.ThreadSafe;
class EventBusImpl implements EventBus { class EventBusImpl implements EventBus {
private final Collection<EventListener> listeners = private final Collection<EventListener> listeners =
new CopyOnWriteArrayList<>(); new CopyOnWriteArrayList<EventListener>();
@Override @Override
public void addListener(EventListener l) { public void addListener(EventListener l) {

View File

@@ -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();
}

View File

@@ -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());
}
}
}

View File

@@ -13,19 +13,23 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CompletionService;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
@@ -41,42 +45,46 @@ class KeyAgreementConnector {
Logger.getLogger(KeyAgreementConnector.class.getName()); Logger.getLogger(KeyAgreementConnector.class.getName());
private final Callbacks callbacks; private final Callbacks callbacks;
private final Clock clock;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final ConnectionChooser connectionChooser; private final CompletionService<KeyAgreementConnection> connect;
private final List<KeyAgreementListener> listeners = private final List<KeyAgreementListener> listeners =
new CopyOnWriteArrayList<>(); new ArrayList<KeyAgreementListener>();
private final CountDownLatch aliceLatch = new CountDownLatch(1); private final List<Future<KeyAgreementConnection>> pending =
private final AtomicBoolean waitingSent = new AtomicBoolean(false); 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, CryptoComponent crypto, PluginManager pluginManager,
ConnectionChooser connectionChooser) { Executor ioExecutor) {
this.callbacks = callbacks; this.callbacks = callbacks;
this.clock = clock;
this.crypto = crypto; this.crypto = crypto;
this.pluginManager = pluginManager; 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"); LOG.info("Starting BQP listeners");
// Derive commitment // Derive commitment
byte[] commitment = crypto.deriveKeyCommitment( byte[] commitment = crypto.deriveKeyCommitment(
localKeyPair.getPublic().getEncoded()); localKeyPair.getPublic().getEncoded());
// Start all listeners and collect their descriptors // Start all listeners and collect their descriptors
List<TransportDescriptor> descriptors = new ArrayList<>(); List<TransportDescriptor> descriptors =
new ArrayList<TransportDescriptor>();
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) { for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
KeyAgreementListener l = KeyAgreementListener l =
plugin.createKeyAgreementListener(commitment); plugin.createKeyAgreementListener(commitment);
if (l != null) { if (l != null) {
TransportId id = plugin.getId(); TransportId id = plugin.getId();
descriptors.add(new TransportDescriptor(id, l.getDescriptor())); 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); listeners.add(l);
connectionChooser.submit(new ReadableTask(l::accept));
} }
} }
return new Payload(commitment, descriptors); return new Payload(commitment, descriptors);
@@ -84,92 +92,128 @@ class KeyAgreementConnector {
void stopListening() { void stopListening() {
LOG.info("Stopping BQP listeners"); LOG.info("Stopping BQP listeners");
stopped = true; for (KeyAgreementListener l : listeners) {
aliceLatch.countDown(); l.close();
for (KeyAgreementListener l : listeners) l.close(); }
connectionChooser.stop(); listeners.clear();
} }
@Nullable @Nullable
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) { public KeyAgreementTransport connect(Payload remotePayload,
// Let the ReadableTasks know if we are Alice boolean alice) {
// Let the listeners know if we are Alice
this.connecting = true;
this.alice = alice; this.alice = alice;
aliceLatch.countDown(); long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
// Start connecting over supported transports // Start connecting over supported transports
if (LOG.isLoggable(INFO)) { LOG.info("Starting outgoing BQP connections");
LOG.info("Starting outgoing BQP connections as "
+ (alice ? "Alice" : "Bob"));
}
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) { for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
Plugin p = pluginManager.getPlugin(d.getId()); Plugin p = pluginManager.getPlugin(d.getId());
if (p instanceof DuplexPlugin) { if (p instanceof DuplexPlugin) {
if (LOG.isLoggable(INFO))
LOG.info("Connecting via " + d.getId());
DuplexPlugin plugin = (DuplexPlugin) p; DuplexPlugin plugin = (DuplexPlugin) p;
byte[] commitment = remotePayload.getCommitment(); pending.add(connect.submit(new ReadableTask(
BdfList descriptor = d.getDescriptor(); new ConnectorTask(plugin, remotePayload.getCommitment(),
connectionChooser.submit(new ReadableTask( d.getDescriptor(), end))));
new ConnectorTask(plugin, commitment, descriptor)));
} }
} }
// Get chosen connection // Get chosen connection
KeyAgreementConnection chosen = null;
try { try {
KeyAgreementConnection chosen = long now = clock.currentTimeMillis();
connectionChooser.poll(CONNECTION_TIMEOUT); Future<KeyAgreementConnection> f =
if (chosen == null) return null; connect.poll(end - now, MILLISECONDS);
if (f == null)
return null; // No task completed within the timeout.
chosen = f.get();
return new KeyAgreementTransport(chosen); return new KeyAgreementTransport(chosen);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.info("Interrupted while waiting for connection"); LOG.info("Interrupted while waiting for connection");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
return null; return null;
} catch (ExecutionException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null; return null;
} finally { } finally {
stopListening(); stopListening();
// Close all other connections
closePending(chosen);
} }
} }
private void waitingForAlice() { private void closePending(@Nullable KeyAgreementConnection chosen) {
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting(); 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 class ConnectorTask implements Callable<KeyAgreementConnection> {
private final byte[] commitment; private final byte[] commitment;
private final BdfList descriptor; private final BdfList descriptor;
private final long end;
private final DuplexPlugin plugin; private final DuplexPlugin plugin;
private ConnectorTask(DuplexPlugin plugin, byte[] commitment, private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
BdfList descriptor) { BdfList descriptor, long end) {
this.plugin = plugin; this.plugin = plugin;
this.commitment = commitment; this.commitment = commitment;
this.descriptor = descriptor; this.descriptor = descriptor;
this.end = end;
} }
@Nullable
@Override @Override
public KeyAgreementConnection call() throws Exception { public KeyAgreementConnection call() throws Exception {
// Repeat attempts until we connect, get stopped, or get interrupted // Repeat attempts until we connect, get interrupted, or time out
while (!stopped) { while (true) {
long now = clock.currentTimeMillis();
if (now > end) throw new IOException();
DuplexTransportConnection conn = DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment, plugin.createKeyAgreementConnection(commitment,
descriptor); descriptor, end - now);
if (conn != null) { if (conn != null) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info(plugin.getId() + ": Outgoing connection"); LOG.info(plugin.getId().getString() +
": Outgoing connection");
return new KeyAgreementConnection(conn, plugin.getId()); return new KeyAgreementConnection(conn, plugin.getId());
} }
// Wait 2s before retry (to circumvent transient failures) // Wait 2s before retry (to circumvent transient failures)
Thread.sleep(2000); Thread.sleep(2000);
} }
return null;
} }
} }
private class ReadableTask implements Callable<KeyAgreementConnection> { private class ReadableTask
implements Callable<KeyAgreementConnection> {
private final Callable<KeyAgreementConnection> connectionTask; private final Callable<KeyAgreementConnection> connectionTask;
@@ -177,23 +221,24 @@ class KeyAgreementConnector {
this.connectionTask = connectionTask; this.connectionTask = connectionTask;
} }
@Nullable
@Override @Override
public KeyAgreementConnection call() throws Exception { public KeyAgreementConnection call() throws Exception {
KeyAgreementConnection c = connectionTask.call(); 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(); InputStream in = c.getConnection().getReader().getInputStream();
while (!stopped && in.available() == 0) { boolean waitingSent = false;
if (LOG.isLoggable(INFO)) while (!alice && in.available() == 0) {
LOG.info(c.getTransportId() + ": Waiting for data"); if (!waitingSent && connecting && !alice) {
waitingForAlice(); // Bob waits here until Alice obtains his payload.
Thread.sleep(500); 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"); LOG.info(c.getTransportId().getString() + ": Data available");
return c; return c;
} }

View File

@@ -1,10 +1,19 @@
package org.briarproject.bramble.keyagreement; 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.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory; 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.KeyAgreementTaskFactory;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser; import org.briarproject.bramble.api.keyagreement.PayloadParser;
import org.briarproject.bramble.api.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.Module;
import dagger.Provides; import dagger.Provides;
@@ -13,9 +22,13 @@ import dagger.Provides;
public class KeyAgreementModule { public class KeyAgreementModule {
@Provides @Provides
KeyAgreementTaskFactory provideKeyAgreementTaskFactory( @Singleton
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) { KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
return keyAgreementTaskFactory; CryptoComponent crypto, EventBus eventBus,
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
PluginManager pluginManager) {
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
ioExecutor, payloadEncoder, pluginManager);
} }
@Provides @Provides
@@ -27,10 +40,4 @@ public class KeyAgreementModule {
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) { PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
return new PayloadParserImpl(bdfReaderFactory); return new PayloadParserImpl(bdfReaderFactory);
} }
@Provides
ConnectionChooser provideConnectionChooser(
ConnectionChooserImpl connectionChooser) {
return connectionChooser;
}
} }

View File

@@ -89,8 +89,7 @@ class KeyAgreementProtocol {
byte[] theirPublicKey; byte[] theirPublicKey;
if (alice) { if (alice) {
sendKey(); sendKey();
// Alice waits here for Bob to scan her QR code, determine his // Alice waits here until Bob obtains her payload.
// role, receive her key and respond with his key
callbacks.connectionWaiting(); callbacks.connectionWaiting();
theirPublicKey = receiveKey(); theirPublicKey = receiveKey();
} else { } else {

View File

@@ -5,37 +5,42 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory; import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager; 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.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory { class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
private final Clock clock;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final EventBus eventBus; private final EventBus eventBus;
private final Executor ioExecutor;
private final PayloadEncoder payloadEncoder; private final PayloadEncoder payloadEncoder;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final Provider<ConnectionChooser> connectionChooserProvider;
@Inject @Inject
KeyAgreementTaskFactoryImpl(CryptoComponent crypto, EventBus eventBus, KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
PayloadEncoder payloadEncoder, PluginManager pluginManager, EventBus eventBus, @IoExecutor Executor ioExecutor,
Provider<ConnectionChooser> connectionChooserProvider) { PayloadEncoder payloadEncoder, PluginManager pluginManager) {
this.clock = clock;
this.crypto = crypto; this.crypto = crypto;
this.eventBus = eventBus; this.eventBus = eventBus;
this.ioExecutor = ioExecutor;
this.payloadEncoder = payloadEncoder; this.payloadEncoder = payloadEncoder;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.connectionChooserProvider = connectionChooserProvider;
} }
@Override @Override
public KeyAgreementTask createTask() { public KeyAgreementTask createTask() {
return new KeyAgreementTaskImpl(crypto, eventBus, payloadEncoder, return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
pluginManager, connectionChooserProvider.get()); pluginManager, ioExecutor);
} }
} }

View File

@@ -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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask, class KeyAgreementTaskImpl extends Thread implements
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks { KeyAgreementTask, KeyAgreementConnector.Callbacks,
KeyAgreementProtocol.Callbacks {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(KeyAgreementTaskImpl.class.getName()); Logger.getLogger(KeyAgreementTaskImpl.class.getName());
@@ -40,15 +43,15 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
private Payload localPayload; private Payload localPayload;
private Payload remotePayload; private Payload remotePayload;
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus, KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
PayloadEncoder payloadEncoder, PluginManager pluginManager, EventBus eventBus, PayloadEncoder payloadEncoder,
ConnectionChooser connectionChooser) { PluginManager pluginManager, Executor ioExecutor) {
this.crypto = crypto; this.crypto = crypto;
this.eventBus = eventBus; this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder; this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair(); localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, crypto, pluginManager, connector = new KeyAgreementConnector(this, clock, crypto,
connectionChooser); pluginManager, ioExecutor);
} }
@Override @Override
@@ -62,8 +65,10 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
@Override @Override
public synchronized void stopListening() { public synchronized void stopListening() {
if (localPayload != null) { if (localPayload != null) {
if (remotePayload == null) connector.stopListening(); if (remotePayload == null)
else interrupt(); connector.stopListening();
else
interrupt();
} }
} }

View File

@@ -51,7 +51,8 @@ class PayloadParserImpl implements PayloadParser {
byte[] commitment = payload.getRaw(1); byte[] commitment = payload.getRaw(1);
if (commitment.length != COMMIT_LENGTH) throw new FormatException(); if (commitment.length != COMMIT_LENGTH) throw new FormatException();
// Remaining elements: transport descriptors // Remaining elements: transport descriptors
List<TransportDescriptor> recognised = new ArrayList<>(); List<TransportDescriptor> recognised =
new ArrayList<TransportDescriptor>();
for (int i = 2; i < payload.size(); i++) { for (int i = 2; i < payload.size(); i++) {
BdfList descriptor = payload.getList(i); BdfList descriptor = payload.getList(i);
long transportId = descriptor.getLong(0); long transportId = descriptor.getLong(0);

View File

@@ -2,11 +2,8 @@ package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.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.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.AuthorFactory; 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.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client; import org.briarproject.bramble.api.sync.Client;
@@ -32,21 +29,14 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.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.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.DB_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class LifecycleManagerImpl implements LifecycleManager, MigrationListener { class LifecycleManagerImpl implements LifecycleManager {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LifecycleManagerImpl.class.getName()); Logger.getLogger(LifecycleManagerImpl.class.getName());
@@ -64,8 +54,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final CountDownLatch startupLatch = new CountDownLatch(1); private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1); private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private volatile LifecycleState state = STARTING;
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
CryptoComponent crypto, AuthorFactory authorFactory, CryptoComponent crypto, AuthorFactory authorFactory,
@@ -75,9 +63,9 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
this.crypto = crypto; this.crypto = crypto;
this.authorFactory = authorFactory; this.authorFactory = authorFactory;
this.identityManager = identityManager; this.identityManager = identityManager;
services = new CopyOnWriteArrayList<>(); services = new CopyOnWriteArrayList<Service>();
clients = new CopyOnWriteArrayList<>(); clients = new CopyOnWriteArrayList<Client>();
executors = new CopyOnWriteArrayList<>(); executors = new CopyOnWriteArrayList<ExecutorService>();
} }
@Override @Override
@@ -100,7 +88,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
executors.add(e); executors.add(e);
} }
private LocalAuthor createLocalAuthor(String nickname) { private LocalAuthor createLocalAuthor(final String nickname) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
KeyPair keyPair = crypto.generateSignatureKeyPair(); KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] publicKey = keyPair.getPublic().getEncoded(); byte[] publicKey = keyPair.getPublic().getEncoded();
@@ -131,7 +119,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Starting services"); LOG.info("Starting services");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
boolean reopened = db.open(this); boolean reopened = db.open();
long duration = System.currentTimeMillis() - start; long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
if (reopened) if (reopened)
@@ -143,10 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
registerLocalAuthor(createLocalAuthor(nickname)); registerLocalAuthor(createLocalAuthor(nickname));
} }
state = STARTING_SERVICES;
dbLatch.countDown(); dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
for (Client c : clients) { for (Client c : clients) {
@@ -172,17 +157,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
+ " took " + duration + " ms"); + " took " + duration + " ms");
} }
} }
state = RUNNING;
startupLatch.countDown(); startupLatch.countDown();
eventBus.broadcast(new LifecycleEvent(RUNNING));
return SUCCESS; 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) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return DB_ERROR; 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 @Override
public void stopServices() { public void stopServices() {
try { try {
@@ -210,8 +180,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
} }
try { try {
LOG.info("Stopping services"); LOG.info("Stopping services");
state = STOPPING; eventBus.broadcast(new ShutdownEvent());
eventBus.broadcast(new LifecycleEvent(STOPPING));
for (Service s : services) { for (Service s : services) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
s.stopService(); s.stopService();
@@ -234,7 +203,9 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Closing database took " + duration + " ms"); LOG.info("Closing database took " + duration + " ms");
shutdownLatch.countDown(); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {
startStopSemaphore.release(); startStopSemaphore.release();
@@ -256,8 +227,4 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
shutdownLatch.await(); shutdownLatch.await();
} }
@Override
public LifecycleState getLifecycleState() {
return state;
}
} }

View File

@@ -37,7 +37,7 @@ public class LifecycleModule {
public LifecycleModule() { public LifecycleModule() {
// The thread pool is unbounded, so use direct handoff // 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 // Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy = RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy(); new ThreadPoolExecutor.DiscardPolicy();

View File

@@ -21,7 +21,7 @@ class ShutdownManagerImpl implements ShutdownManager {
private int nextHandle = 0; private int nextHandle = 0;
ShutdownManagerImpl() { ShutdownManagerImpl() {
hooks = new HashMap<>(); hooks = new HashMap<Integer, Thread>();
} }
@Override @Override

View File

@@ -134,7 +134,11 @@ class ConnectionManagerImpl implements ConnectionManager {
try { try {
byte[] tag = readTag(reader); byte[] tag = readTag(reader);
ctx = keyManager.getStreamContext(transportId, tag); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeReader(true, false); disposeReader(true, false);
return; return;
@@ -245,7 +249,11 @@ class ConnectionManagerImpl implements ConnectionManager {
try { try {
byte[] tag = readTag(reader); byte[] tag = readTag(reader);
ctx = keyManager.getStreamContext(transportId, tag); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeReader(true, false); disposeReader(true, false);
return; return;
@@ -258,7 +266,12 @@ class ConnectionManagerImpl implements ConnectionManager {
contactId = ctx.getContactId(); contactId = ctx.getContactId();
connectionRegistry.registerConnection(contactId, transportId, true); connectionRegistry.registerConnection(contactId, transportId, true);
// Start the outgoing session on another thread // Start the outgoing session on another thread
ioExecutor.execute(this::runOutgoingSession); ioExecutor.execute(new Runnable() {
@Override
public void run() {
runOutgoingSession();
}
});
try { try {
// Create and run the incoming session // Create and run the incoming session
incomingSession = createIncomingSession(ctx, reader); incomingSession = createIncomingSession(ctx, reader);
@@ -355,7 +368,12 @@ class ConnectionManagerImpl implements ConnectionManager {
return; return;
} }
// Start the incoming session on another thread // Start the incoming session on another thread
ioExecutor.execute(this::runIncomingSession); ioExecutor.execute(new Runnable() {
@Override
public void run() {
runIncomingSession();
}
});
try { try {
// Create and run the outgoing session // Create and run the outgoing session
outgoingSession = createDuplexOutgoingSession(ctx, writer); outgoingSession = createDuplexOutgoingSession(ctx, writer);
@@ -373,7 +391,11 @@ class ConnectionManagerImpl implements ConnectionManager {
try { try {
byte[] tag = readTag(reader); byte[] tag = readTag(reader);
ctx = keyManager.getStreamContext(transportId, tag); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeReader(true, false); disposeReader(true, false);
return; return;

View File

@@ -42,8 +42,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
@Inject @Inject
ConnectionRegistryImpl(EventBus eventBus) { ConnectionRegistryImpl(EventBus eventBus) {
this.eventBus = eventBus; this.eventBus = eventBus;
connections = new HashMap<>(); connections = new HashMap<TransportId, Map<ContactId, Integer>>();
contactCounts = new HashMap<>(); contactCounts = new HashMap<ContactId, Integer>();
} }
@Override @Override
@@ -58,7 +58,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
try { try {
Map<ContactId, Integer> m = connections.get(t); Map<ContactId, Integer> m = connections.get(t);
if (m == null) { if (m == null) {
m = new HashMap<>(); m = new HashMap<ContactId, Integer>();
connections.put(t, m); connections.put(t, m);
} }
Integer count = m.get(c); Integer count = m.get(c);
@@ -124,7 +124,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
try { try {
Map<ContactId, Integer> m = connections.get(t); Map<ContactId, Integer> m = connections.get(t);
if (m == null) return Collections.emptyList(); 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)) if (LOG.isLoggable(INFO))
LOG.info(ids.size() + " contacts connected"); LOG.info(ids.size() + " contacts connected");
return ids; return ids;

View File

@@ -82,10 +82,10 @@ class PluginManagerImpl implements PluginManager, Service {
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.transportPropertyManager = transportPropertyManager; this.transportPropertyManager = transportPropertyManager;
this.uiCallback = uiCallback; this.uiCallback = uiCallback;
plugins = new ConcurrentHashMap<>(); plugins = new ConcurrentHashMap<TransportId, Plugin>();
simplexPlugins = new CopyOnWriteArrayList<>(); simplexPlugins = new CopyOnWriteArrayList<SimplexPlugin>();
duplexPlugins = new CopyOnWriteArrayList<>(); duplexPlugins = new CopyOnWriteArrayList<DuplexPlugin>();
startLatches = new ConcurrentHashMap<>(); startLatches = new ConcurrentHashMap<TransportId, CountDownLatch>();
} }
@Override @Override
@@ -156,17 +156,17 @@ class PluginManagerImpl implements PluginManager, Service {
@Override @Override
public Collection<SimplexPlugin> getSimplexPlugins() { public Collection<SimplexPlugin> getSimplexPlugins() {
return new ArrayList<>(simplexPlugins); return new ArrayList<SimplexPlugin>(simplexPlugins);
} }
@Override @Override
public Collection<DuplexPlugin> getDuplexPlugins() { public Collection<DuplexPlugin> getDuplexPlugins() {
return new ArrayList<>(duplexPlugins); return new ArrayList<DuplexPlugin>(duplexPlugins);
} }
@Override @Override
public Collection<DuplexPlugin> getKeyAgreementPlugins() { public Collection<DuplexPlugin> getKeyAgreementPlugins() {
List<DuplexPlugin> supported = new ArrayList<>(); List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
for (DuplexPlugin d : duplexPlugins) for (DuplexPlugin d : duplexPlugins)
if (d.supportsKeyAgreement()) supported.add(d); if (d.supportsKeyAgreement()) supported.add(d);
return supported; return supported;

View File

@@ -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.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; 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.event.TransportEnabledEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin; import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
@@ -26,7 +25,6 @@ import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -52,7 +50,7 @@ class Poller implements EventListener {
private final SecureRandom random; private final SecureRandom random;
private final Clock clock; private final Clock clock;
private final Lock lock; private final Lock lock;
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock private final Map<TransportId, PollTask> tasks; // Locking: lock
@Inject @Inject
Poller(@IoExecutor Executor ioExecutor, Poller(@IoExecutor Executor ioExecutor,
@@ -68,7 +66,7 @@ class Poller implements EventListener {
this.random = random; this.random = random;
this.clock = clock; this.clock = clock;
lock = new ReentrantLock(); lock = new ReentrantLock();
tasks = new HashMap<>(); tasks = new HashMap<TransportId, PollTask>();
} }
@Override @Override
@@ -95,10 +93,6 @@ class Poller implements EventListener {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportEnabledEvent t = (TransportEnabledEvent) e;
// Poll the newly enabled transport // Poll the newly enabled transport
pollNow(t.getTransportId()); 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); connectToContact(c, (DuplexPlugin) p);
} }
private void connectToContact(ContactId c, SimplexPlugin p) { private void connectToContact(final ContactId c, final SimplexPlugin p) {
ioExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
TransportId t = p.getId(); @Override
if (!connectionRegistry.isConnected(c, t)) { public void run() {
TransportConnectionWriter w = p.createWriter(c); TransportId t = p.getId();
if (w != null) if (!connectionRegistry.isConnected(c, t)) {
connectionManager.manageOutgoingConnection(c, t, w); TransportConnectionWriter w = p.createWriter(c);
if (w != null)
connectionManager.manageOutgoingConnection(c, t, w);
}
} }
}); });
} }
private void connectToContact(ContactId c, DuplexPlugin p) { private void connectToContact(final ContactId c, final DuplexPlugin p) {
ioExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
TransportId t = p.getId(); @Override
if (!connectionRegistry.isConnected(c, t)) { public void run() {
DuplexTransportConnection d = p.createConnection(c); TransportId t = p.getId();
if (d != null) if (!connectionRegistry.isConnected(c, t)) {
connectionManager.manageOutgoingConnection(c, t, d); 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(); TransportId t = p.getId();
lock.lock(); lock.lock();
try { try {
ScheduledPollTask scheduled = tasks.get(t); PollTask scheduled = tasks.get(t);
if (scheduled == null || due < scheduled.task.due) { if (scheduled == null || due < scheduled.due) {
// If a later task exists, cancel it. If it's already started final PollTask task = new PollTask(p, due, randomiseNext);
// it will abort safely when it finds it's been replaced tasks.put(t, task);
if (scheduled != null) scheduled.future.cancel(false); scheduler.schedule(new Runnable() {
PollTask task = new PollTask(p, due, randomiseNext); @Override
Future future = scheduler.schedule( public void run() {
() -> ioExecutor.execute(task), delay, MILLISECONDS); ioExecutor.execute(task);
tasks.put(t, new ScheduledPollTask(task, future)); }
}, delay, MILLISECONDS);
} }
} finally { } finally {
lock.unlock(); 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 @IoExecutor
private void poll(Plugin p) { private void poll(final Plugin p) {
TransportId t = p.getId(); TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t); if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
p.poll(connectionRegistry.getConnectedContacts(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 class PollTask implements Runnable {
private final Plugin plugin; private final Plugin plugin;
@@ -218,9 +198,7 @@ class Poller implements EventListener {
lock.lock(); lock.lock();
try { try {
TransportId t = plugin.getId(); TransportId t = plugin.getId();
ScheduledPollTask scheduled = tasks.get(t); if (tasks.get(t) != this) return; // Replaced by another task
if (scheduled != null && scheduled.task != this)
return; // Replaced by another task
tasks.remove(t); tasks.remove(t);
} finally { } finally {
lock.unlock(); lock.unlock();

View File

@@ -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);
}
}
}

View File

@@ -108,7 +108,7 @@ abstract class FilePlugin implements SimplexPlugin {
} }
} }
protected void createReaderFromFile(File f) { protected void createReaderFromFile(final File f) {
if (!running) return; if (!running) return;
ioExecutor.execute(new ReaderCreator(f)); ioExecutor.execute(new ReaderCreator(f));
} }

View File

@@ -23,8 +23,9 @@ import java.net.SocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -43,9 +44,6 @@ class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LanTcpPlugin.class.getName()); Logger.getLogger(LanTcpPlugin.class.getName());
private static final LanAddressComparator ADDRESS_COMPARATOR =
new LanAddressComparator();
private static final int MAX_ADDRESSES = 4; private static final int MAX_ADDRESSES = 4;
private static final String SEPARATOR = ","; private static final String SEPARATOR = ",";
@@ -65,25 +63,26 @@ class LanTcpPlugin extends TcpPlugin {
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
String oldIpPorts = p.get(PROP_IP_PORTS); String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts); List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>(); List<InetSocketAddress> locals = new LinkedList<InetSocketAddress>();
for (InetAddress local : getLocalIpAddresses()) { for (InetAddress local : getLocalIpAddresses()) {
if (isAcceptableAddress(local)) { if (isAcceptableAddress(local)) {
// If this is the old address, try to use the same port // If this is the old address, try to use the same port
for (InetSocketAddress old : olds) { for (InetSocketAddress old : olds) {
if (old.getAddress().equals(local)) if (old.getAddress().equals(local)) {
locals.add(new InetSocketAddress(local, old.getPort())); int port = old.getPort();
locals.add(0, new InetSocketAddress(local, port));
}
} }
locals.add(new InetSocketAddress(local, 0)); locals.add(new InetSocketAddress(local, 0));
} }
} }
Collections.sort(locals, ADDRESS_COMPARATOR);
return locals; return locals;
} }
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) { private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList(); if (StringUtils.isNullOrEmpty(ipPorts)) return Collections.emptyList();
String[] split = ipPorts.split(SEPARATOR); String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> addresses = new ArrayList<>(); List<InetSocketAddress> addresses = new ArrayList<InetSocketAddress>();
for (String ipPort : split) { for (String ipPort : split) {
InetSocketAddress a = parseSocketAddress(ipPort); InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) addresses.add(a); if (a != null) addresses.add(a);
@@ -96,7 +95,7 @@ class LanTcpPlugin extends TcpPlugin {
String ipPort = getIpPortString(a); String ipPort = getIpPortString(a);
// Get the list of recently used addresses // Get the list of recently used addresses
String setting = callback.getSettings().get(PREF_LAN_IP_PORTS); String setting = callback.getSettings().get(PREF_LAN_IP_PORTS);
List<String> recent = new ArrayList<>(); List<String> recent = new ArrayList<String>();
if (!StringUtils.isNullOrEmpty(setting)) if (!StringUtils.isNullOrEmpty(setting))
Collections.addAll(recent, setting.split(SEPARATOR)); Collections.addAll(recent, setting.split(SEPARATOR));
// Is the address already in the list? // Is the address already in the list?
@@ -112,7 +111,7 @@ class LanTcpPlugin extends TcpPlugin {
recent = recent.subList(0, MAX_ADDRESSES); recent = recent.subList(0, MAX_ADDRESSES);
setting = StringUtils.join(recent, SEPARATOR); setting = StringUtils.join(recent, SEPARATOR);
// Update the list of addresses shared with contacts // Update the list of addresses shared with contacts
List<String> shared = new ArrayList<>(recent); List<String> shared = new ArrayList<String>(recent);
Collections.sort(shared); Collections.sort(shared);
String property = StringUtils.join(shared, SEPARATOR); String property = StringUtils.join(shared, SEPARATOR);
TransportProperties properties = new TransportProperties(); TransportProperties properties = new TransportProperties();
@@ -154,39 +153,17 @@ class LanTcpPlugin extends TcpPlugin {
// Package access for testing // Package access for testing
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) { boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
// 10.0.0.0/8 // 10.0.0.0/8
if (isPrefix10(localIp)) return isPrefix10(remoteIp); if (localIp[0] == 10) return remoteIp[0] == 10;
// 172.16.0.0/12 // 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 // 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 // Unrecognised prefix - may be compatible
return true; 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 @Override
public boolean supportsKeyAgreement() { public boolean supportsKeyAgreement() {
return true; return true;
@@ -223,7 +200,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null; if (!isRunning()) return null;
InetSocketAddress remote; InetSocketAddress remote;
try { try {
@@ -282,11 +259,18 @@ class LanTcpPlugin extends TcpPlugin {
} }
@Override @Override
public KeyAgreementConnection accept() throws IOException { public Callable<KeyAgreementConnection> listen() {
Socket s = ss.accept(); return new Callable<KeyAgreementConnection>() {
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection"); @Override
return new KeyAgreementConnection(new TcpTransportConnection( public KeyAgreementConnection call() throws IOException {
LanTcpPlugin.this, s), ID); Socket s = ss.accept();
if (LOG.isLoggable(INFO))
LOG.info(ID.getString() + ": Incoming connection");
return new KeyAgreementConnection(
new TcpTransportConnection(LanTcpPlugin.this, s),
ID);
}
};
} }
@Override @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;
}
}
} }

View File

@@ -37,7 +37,7 @@ class PortMapperImpl implements PortMapper {
} }
@Override @Override
public MappingResult map(int port) { public MappingResult map(final int port) {
if (!started.getAndSet(true)) start(); if (!started.getAndSet(true)) start();
if (gateway == null) return null; if (gateway == null) return null;
InetAddress internal = gateway.getLocalAddress(); InetAddress internal = gateway.getLocalAddress();
@@ -50,7 +50,12 @@ class PortMapperImpl implements PortMapper {
succeeded = gateway.addPortMapping(port, port, succeeded = gateway.addPortMapping(port, port,
getHostAddress(internal), "TCP", "TCP"); getHostAddress(internal), "TCP", "TCP");
if (succeeded) { if (succeeded) {
shutdownManager.addShutdownHook(() -> deleteMapping(port)); shutdownManager.addShutdownHook(new Runnable() {
@Override
public void run() {
deleteMapping(port);
}
});
} }
String externalString = gateway.getExternalIPAddress(); String externalString = gateway.getExternalIPAddress();
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -58,7 +63,9 @@ class PortMapperImpl implements PortMapper {
"External address " + scrubInetAddress(externalString)); "External address " + scrubInetAddress(externalString));
if (externalString != null) if (externalString != null)
external = InetAddress.getByName(externalString); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
return new MappingResult(internal, external, port, succeeded); return new MappingResult(internal, external, port, succeeded);
@@ -75,7 +82,11 @@ class PortMapperImpl implements PortMapper {
GatewayDiscover d = new GatewayDiscover(); GatewayDiscover d = new GatewayDiscover();
try { try {
d.discover(); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
gateway = d.getValidGateway(); gateway = d.getValidGateway();
@@ -86,7 +97,9 @@ class PortMapperImpl implements PortMapper {
gateway.deletePortMapping(port, "TCP"); gateway.deletePortMapping(port, "TCP");
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Deleted mapping for port " + port); 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); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
} }

View File

@@ -110,37 +110,41 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
protected void bind() { protected void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
if (!running) return; @Override
ServerSocket ss = null; public void run() {
for (InetSocketAddress addr : getLocalSocketAddresses()) { if (!running) return;
try { ServerSocket ss = null;
ss = new ServerSocket(); for (InetSocketAddress addr : getLocalSocketAddresses()) {
ss.bind(addr); try {
break; ss = new ServerSocket();
} catch (IOException e) { ss.bind(addr);
if (LOG.isLoggable(INFO)) break;
LOG.info("Failed to bind " + scrubSocketAddress(addr)); } catch (IOException e) {
tryToClose(ss); 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) { private void connectAndCallBack(final ContactId c,
ioExecutor.execute(() -> { final TransportProperties p) {
if (!isRunning()) return; ioExecutor.execute(new Runnable() {
DuplexTransportConnection d = createConnection(p); @Override
if (d != null) { public void run() {
backoff.reset(); if (!isRunning()) return;
callback.outgoingConnectionCreated(c, d); DuplexTransportConnection d = createConnection(p);
if (d != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, d);
}
} }
}); });
} }
@@ -297,7 +305,7 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor, long timeout) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@@ -309,7 +317,7 @@ abstract class TcpPlugin implements DuplexPlugin {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return Collections.emptyList(); return Collections.emptyList();
} }
List<InetAddress> addrs = new ArrayList<>(); List<InetAddress> addrs = new ArrayList<InetAddress>();
for (NetworkInterface iface : ifaces) for (NetworkInterface iface : ifaces)
addrs.addAll(Collections.list(iface.getInetAddresses())); addrs.addAll(Collections.list(iface.getInetAddresses()));
return addrs; return addrs;

View File

@@ -43,7 +43,7 @@ class WanTcpPlugin extends TcpPlugin {
// Use the same address and port as last time if available // Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT)); InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT));
List<InetSocketAddress> addrs = new LinkedList<>(); List<InetSocketAddress> addrs = new LinkedList<InetSocketAddress>();
for (InetAddress a : getLocalIpAddresses()) { for (InetAddress a : getLocalIpAddresses()) {
if (isAcceptableAddress(a)) { if (isAcceptableAddress(a)) {
// If this is the old address, try to use the same port // If this is the old address, try to use the same port

View File

@@ -40,12 +40,9 @@ public class PropertiesModule {
@Provides @Provides
@Singleton @Singleton
TransportPropertyManager getTransportPropertyManager( TransportPropertyManager getTransportPropertyManager(
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager, ContactManager contactManager,
ValidationManager validationManager, ContactManager contactManager,
TransportPropertyManagerImpl transportPropertyManager) { TransportPropertyManagerImpl transportPropertyManager) {
lifecycleManager.registerClient(transportPropertyManager); lifecycleManager.registerClient(transportPropertyManager);
validationManager.registerIncomingMessageHook(CLIENT_ID,
transportPropertyManager);
contactManager.registerAddContactHook(transportPropertyManager); contactManager.registerAddContactHook(transportPropertyManager);
contactManager.registerRemoveContactHook(transportPropertyManager); contactManager.registerRemoveContactHook(transportPropertyManager);
return transportPropertyManager; return transportPropertyManager;

View File

@@ -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.contact.ContactManager.RemoveContactHook;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; 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.Client;
import org.briarproject.bramble.api.sync.Group; import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import java.util.HashMap; import java.util.HashMap;
@@ -40,22 +36,20 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class TransportPropertyManagerImpl implements TransportPropertyManager, class TransportPropertyManagerImpl implements TransportPropertyManager,
Client, AddContactHook, RemoveContactHook, IncomingMessageHook { Client, AddContactHook, RemoveContactHook {
private final DatabaseComponent db; private final DatabaseComponent db;
private final ClientHelper clientHelper; private final ClientHelper clientHelper;
private final MetadataParser metadataParser;
private final ContactGroupFactory contactGroupFactory; private final ContactGroupFactory contactGroupFactory;
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
@Inject @Inject
TransportPropertyManagerImpl(DatabaseComponent db, TransportPropertyManagerImpl(DatabaseComponent db,
ClientHelper clientHelper, MetadataParser metadataParser, ClientHelper clientHelper, ContactGroupFactory contactGroupFactory,
ContactGroupFactory contactGroupFactory, Clock clock) { Clock clock) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
this.clock = clock; this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID); localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
@@ -63,7 +57,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public void createLocalState(Transaction txn) throws DbException { public void createLocalState(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return;
db.addGroup(txn, localGroup); db.addGroup(txn, localGroup);
// Ensure we've set things up for any pre-existing contacts // Ensure we've set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c); for (Contact c : db.getContacts(txn)) addingContact(txn, c);
@@ -91,31 +84,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
db.removeGroup(txn, getContactGroup(c)); 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 @Override
public void addRemoteProperties(Transaction txn, ContactId c, public void addRemoteProperties(Transaction txn, ContactId c,
Map<TransportId, TransportProperties> props) throws DbException { Map<TransportId, TransportProperties> props) throws DbException {
@@ -130,8 +98,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<TransportId, TransportProperties> getLocalProperties() public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException { throws DbException {
Map<TransportId, TransportProperties> local; Map<TransportId, TransportProperties> local;
// TODO: Transaction can be read-only when code is simplified Transaction txn = db.startTransaction(true);
Transaction txn = db.startTransaction(false);
try { try {
local = getLocalProperties(txn); local = getLocalProperties(txn);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -145,9 +112,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<TransportId, TransportProperties> getLocalProperties( public Map<TransportId, TransportProperties> getLocalProperties(
Transaction txn) throws DbException { Transaction txn) throws DbException {
try { try {
Map<TransportId, TransportProperties> local = new HashMap<>(); Map<TransportId, TransportProperties> local =
new HashMap<TransportId, TransportProperties>();
// Find the latest local update for each transport // 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 // Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) { for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
BdfList message = clientHelper.getMessageAsList(txn, BdfList message = clientHelper.getMessageAsList(txn,
@@ -166,8 +135,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
throws DbException { throws DbException {
try { try {
TransportProperties p = null; TransportProperties p = null;
// TODO: Transaction can be read-only when code is simplified Transaction txn = db.startTransaction(true);
Transaction txn = db.startTransaction(false);
try { try {
// Find the latest local update // Find the latest local update
LatestUpdate latest = findLatest(txn, localGroup.getId(), t, LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
@@ -192,9 +160,9 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Override @Override
public Map<ContactId, TransportProperties> getRemoteProperties( public Map<ContactId, TransportProperties> getRemoteProperties(
TransportId t) throws DbException { TransportId t) throws DbException {
Map<ContactId, TransportProperties> remote = new HashMap<>(); Map<ContactId, TransportProperties> remote =
// TODO: Transaction can be read-only when code is simplified new HashMap<ContactId, TransportProperties>();
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(true);
try { try {
for (Contact c : db.getContacts(txn)) for (Contact c : db.getContacts(txn))
remote.put(c.getId(), getRemoteProperties(txn, c, t)); remote.put(c.getId(), getRemoteProperties(txn, c, t));
@@ -228,8 +196,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public TransportProperties getRemoteProperties(ContactId c, TransportId t) public TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException { throws DbException {
TransportProperties p; TransportProperties p;
// TODO: Transaction can be read-only when code is simplified Transaction txn = db.startTransaction(true);
Transaction txn = db.startTransaction(false);
try { try {
p = getRemoteProperties(txn, db.getContact(txn, c), t); p = getRemoteProperties(txn, db.getContact(txn, c), t);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -267,9 +234,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
long version = latest == null ? 1 : latest.version + 1; long version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, localGroup.getId(), t, merged, version, storeMessage(txn, localGroup.getId(), t, merged, version,
true, false); 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 // Store the merged properties in each contact's group
for (Contact c : db.getContacts(txn)) { for (Contact c : db.getContacts(txn)) {
Group g = getContactGroup(c); Group g = getContactGroup(c);
@@ -277,9 +241,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
version = latest == null ? 1 : latest.version + 1; version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, g.getId(), t, merged, version, storeMessage(txn, g.getId(), t, merged, version,
true, true); true, true);
// Delete the previous update, if any
if (latest != null)
db.removeMessage(txn, latest.messageId);
} }
} }
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -317,26 +278,20 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return BdfList.of(t.getString(), version, p); return BdfList.of(t.getString(), version, p);
} }
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn) private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
throws DbException, FormatException { GroupId g, boolean local) throws DbException, FormatException {
// TODO: This can be simplified before 1.0 Map<TransportId, LatestUpdate> latestUpdates =
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>(); new HashMap<TransportId, LatestUpdate>();
Map<MessageId, BdfDictionary> metadata = clientHelper Map<MessageId, BdfDictionary> metadata =
.getMessageMetadataAsDictionary(txn, localGroup.getId()); clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) { for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue(); BdfDictionary meta = e.getValue();
TransportId t = new TransportId(meta.getString("transportId")); if (meta.getBoolean("local") == local) {
long version = meta.getLong("version"); TransportId t = new TransportId(meta.getString("transportId"));
LatestUpdate latest = latestUpdates.get(t); long version = meta.getLong("version");
if (latest == null) { LatestUpdate latest = latestUpdates.get(t);
latestUpdates.put(t, new LatestUpdate(e.getKey(), version)); if (latest == null || version > latest.version)
} else if (version > latest.version) { latestUpdates.put(t, new LatestUpdate(e.getKey(), 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());
} }
} }
return latestUpdates; return latestUpdates;
@@ -345,7 +300,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Nullable @Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t, private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
boolean local) throws DbException, FormatException { boolean local) throws DbException, FormatException {
// TODO: This can be simplified before 1.0
LatestUpdate latest = null; LatestUpdate latest = null;
Map<MessageId, BdfDictionary> metadata = Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g); clientHelper.getMessageMetadataAsDictionary(txn, g);
@@ -354,26 +308,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
if (meta.getString("transportId").equals(t.getString()) if (meta.getString("transportId").equals(t.getString())
&& meta.getBoolean("local") == local) { && meta.getBoolean("local") == local) {
long version = meta.getLong("version"); long version = meta.getLong("version");
if (latest == null) { if (latest == null || version > latest.version)
latest = new LatestUpdate(e.getKey(), 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; return latest;

View File

@@ -41,7 +41,7 @@ class Receiver implements ReadHandler {
Receiver(Clock clock, Sender sender) { Receiver(Clock clock, Sender sender) {
this.sender = sender; this.sender = sender;
this.clock = clock; this.clock = clock;
dataFrames = new TreeSet<>(new SequenceNumberComparator()); dataFrames = new TreeSet<Data>(new SequenceNumberComparator());
} }
Data read() throws IOException, InterruptedException { Data read() throws IOException, InterruptedException {

View File

@@ -42,44 +42,48 @@ class ReliabilityLayerImpl implements ReliabilityLayer, WriteHandler {
this.executor = executor; this.executor = executor;
this.clock = clock; this.clock = clock;
this.writeHandler = writeHandler; this.writeHandler = writeHandler;
writes = new LinkedBlockingQueue<>(); writes = new LinkedBlockingQueue<byte[]>();
} }
@Override @Override
public void start() { public void start() {
SlipEncoder encoder = new SlipEncoder(this); SlipEncoder encoder = new SlipEncoder(this);
Sender sender = new Sender(clock, encoder); final Sender sender = new Sender(clock, encoder);
receiver = new Receiver(clock, sender); receiver = new Receiver(clock, sender);
decoder = new SlipDecoder(receiver, Data.MAX_LENGTH); decoder = new SlipDecoder(receiver, Data.MAX_LENGTH);
inputStream = new ReceiverInputStream(receiver); inputStream = new ReceiverInputStream(receiver);
outputStream = new SenderOutputStream(sender); outputStream = new SenderOutputStream(sender);
running = true; running = true;
executor.execute(() -> { executor.execute(new Runnable() {
long now = clock.currentTimeMillis(); @Override
long next = now + TICK_INTERVAL; public void run() {
try { long now = clock.currentTimeMillis();
while (running) { long next = now + TICK_INTERVAL;
byte[] b = null; try {
while (now < next && b == null) { while (running) {
b = writes.poll(next - now, MILLISECONDS); byte[] b = null;
if (!running) return; while (now < next && b == null) {
now = clock.currentTimeMillis(); b = writes.poll(next - now, MILLISECONDS);
} if (!running) return;
if (b == null) { now = clock.currentTimeMillis();
sender.tick(); }
while (next <= now) next += TICK_INTERVAL; if (b == null) {
} else { sender.tick();
if (b.length == 0) return; // Poison pill while (next <= now) next += TICK_INTERVAL;
writeHandler.handleWrite(b); } 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;
} }
}); });
} }

View File

@@ -46,7 +46,7 @@ class Sender {
Sender(Clock clock, WriteHandler writeHandler) { Sender(Clock clock, WriteHandler writeHandler) {
this.clock = clock; this.clock = clock;
this.writeHandler = writeHandler; this.writeHandler = writeHandler;
outstanding = new LinkedList<>(); outstanding = new LinkedList<Outstanding>();
} }
void sendAck(long sequenceNumber, int windowSize) throws IOException { void sendAck(long sequenceNumber, int windowSize) throws IOException {
@@ -136,7 +136,7 @@ class Sender {
if (now - o.lastTransmitted > rto) { if (now - o.lastTransmitted > rto) {
it.remove(); it.remove();
if (retransmit == null) if (retransmit == null)
retransmit = new ArrayList<>(); retransmit = new ArrayList<Outstanding>();
retransmit.add(o); retransmit.add(o);
// Update the retransmission timeout // Update the retransmission timeout
rto <<= 1; rto <<= 1;

View File

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
@@ -29,8 +29,6 @@ import java.util.Collection;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; 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.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH; 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 @NotNullByDefault
class DuplexOutgoingSession implements SyncSession, EventListener { 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 = private static final Logger LOG =
Logger.getLogger(DuplexOutgoingSession.class.getName()); Logger.getLogger(DuplexOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE = () -> { private static final ThrowingRunnable<IOException> CLOSE =
}; new ThrowingRunnable<IOException>() {
private static final ThrowingRunnable<IOException> @Override
NEXT_SEND_TIME_DECREASED = () -> { public void run() {
}; }
};
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
@@ -70,13 +70,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final RecordWriter recordWriter; private final RecordWriter recordWriter;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks; 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; private volatile boolean interrupted = false;
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
@@ -90,7 +83,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime; this.maxIdleTime = maxIdleTime;
this.recordWriter = recordWriter; this.recordWriter = recordWriter;
writerTasks = new LinkedBlockingQueue<>(); writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
} }
@IoExecutor @IoExecutor
@@ -99,21 +92,21 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
eventBus.addListener(this); eventBus.addListener(this);
try { try {
// Start a query for each type of record // Start a query for each type of record
generateAck(); dbExecutor.execute(new GenerateAck());
generateBatch(); dbExecutor.execute(new GenerateBatch());
generateOffer(); dbExecutor.execute(new GenerateOffer());
generateRequest(); dbExecutor.execute(new GenerateRequest());
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long nextKeepalive = now + maxIdleTime; long nextKeepalive = now + maxIdleTime;
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
boolean dataToFlush = true; boolean dataToFlush = true;
// Write records until interrupted // Write records until interrupted
try { try {
while (!interrupted) { while (!interrupted) {
// Work out how long we should wait for a record // Work out how long we should wait for a record
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
long keepaliveWait = Math.max(0, nextKeepalive - now); long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
long sendWait = Math.max(0, nextSendTime.get() - now); if (wait < 0) wait = 0;
long wait = Math.min(keepaliveWait, sendWait);
// Flush any unflushed data if we're going to wait // Flush any unflushed data if we're going to wait
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) { if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
recordWriter.flush(); recordWriter.flush();
@@ -125,25 +118,20 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
MILLISECONDS); MILLISECONDS);
if (task == null) { if (task == null) {
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
if (now >= nextSendTime.get()) { if (now >= nextRetxQuery) {
// Check for retransmittable messages // Check for retransmittable records
LOG.info("Checking for retransmittable messages"); dbExecutor.execute(new GenerateBatch());
setNextSendTime(Long.MAX_VALUE); dbExecutor.execute(new GenerateOffer());
generateBatch(); nextRetxQuery = now + RETX_QUERY_INTERVAL;
generateOffer();
} }
if (now >= nextKeepalive) { if (now >= nextKeepalive) {
// Flush the stream to keep it alive // Flush the stream to keep it alive
LOG.info("Sending keepalive");
recordWriter.flush(); recordWriter.flush();
dataToFlush = false; dataToFlush = false;
nextKeepalive = now + maxIdleTime; nextKeepalive = now + maxIdleTime;
} }
} else if (task == CLOSE) { } else if (task == CLOSE) {
LOG.info("Closed");
break; break;
} else if (task == NEXT_SEND_TIME_DECREASED) {
LOG.info("Next send time decreased");
} else { } else {
task.run(); task.run();
dataToFlush = true; 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 @Override
public void interrupt() { public void interrupt() {
interrupted = true; interrupted = true;
@@ -196,23 +159,22 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof MessageSharedEvent) { } else if (e instanceof MessageSharedEvent) {
generateOffer(); dbExecutor.execute(new GenerateOffer());
} else if (e instanceof GroupVisibilityUpdatedEvent) { } else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e; GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getAffectedContacts().contains(contactId)) if (g.getAffectedContacts().contains(contactId))
generateOffer(); dbExecutor.execute(new GenerateOffer());
} else if (e instanceof MessageRequestedEvent) { } else if (e instanceof MessageRequestedEvent) {
if (((MessageRequestedEvent) e).getContactId().equals(contactId)) if (((MessageRequestedEvent) e).getContactId().equals(contactId))
generateBatch(); dbExecutor.execute(new GenerateBatch());
} else if (e instanceof MessageToAckEvent) { } else if (e instanceof MessageToAckEvent) {
if (((MessageToAckEvent) e).getContactId().equals(contactId)) if (((MessageToAckEvent) e).getContactId().equals(contactId))
generateAck(); dbExecutor.execute(new GenerateAck());
} else if (e instanceof MessageToRequestEvent) { } else if (e instanceof MessageToRequestEvent) {
if (((MessageToRequestEvent) e).getContactId().equals(contactId)) if (((MessageToRequestEvent) e).getContactId().equals(contactId))
generateRequest(); dbExecutor.execute(new GenerateRequest());
} else if (e instanceof LifecycleEvent) { } else if (e instanceof ShutdownEvent) {
LifecycleEvent l = (LifecycleEvent) e; interrupt();
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }
@@ -222,7 +184,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override @Override
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
if (!generateAckQueued.getAndSet(false)) throw new AssertionError();
try { try {
Ack a; Ack a;
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
@@ -256,7 +217,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return; if (interrupted) return;
recordWriter.writeAck(ack); recordWriter.writeAck(ack);
LOG.info("Sent ack"); LOG.info("Sent ack");
generateAck(); dbExecutor.execute(new GenerateAck());
} }
} }
@@ -266,15 +227,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override @Override
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
if (!generateBatchQueued.getAndSet(false))
throw new AssertionError();
try { try {
Collection<byte[]> b; Collection<byte[]> b;
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
b = db.generateRequestedBatch(txn, contactId, b = db.generateRequestedBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency); MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -303,7 +261,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return; if (interrupted) return;
for (byte[] raw : batch) recordWriter.writeMessage(raw); for (byte[] raw : batch) recordWriter.writeMessage(raw);
LOG.info("Sent batch"); LOG.info("Sent batch");
generateBatch(); dbExecutor.execute(new GenerateBatch());
} }
} }
@@ -313,15 +271,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override @Override
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
if (!generateOfferQueued.getAndSet(false))
throw new AssertionError();
try { try {
Offer o; Offer o;
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS, o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
maxLatency); maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -350,7 +305,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return; if (interrupted) return;
recordWriter.writeOffer(offer); recordWriter.writeOffer(offer);
LOG.info("Sent offer"); LOG.info("Sent offer");
generateOffer(); dbExecutor.execute(new GenerateOffer());
} }
} }
@@ -360,8 +315,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
@Override @Override
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
if (!generateRequestQueued.getAndSet(false))
throw new AssertionError();
try { try {
Request r; Request r;
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
@@ -395,7 +348,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
if (interrupted) return; if (interrupted) return;
recordWriter.writeRequest(request); recordWriter.writeRequest(request);
LOG.info("Sent request"); LOG.info("Sent request");
generateRequest(); dbExecutor.execute(new GenerateRequest());
} }
} }
} }

View File

@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
/** /**
* An incoming {@link SyncSession}. * An incoming {@link SyncSession}.
@@ -97,9 +96,8 @@ class IncomingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) { if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof LifecycleEvent) { } else if (e instanceof ShutdownEvent) {
LifecycleEvent l = (LifecycleEvent) e; interrupt();
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }

View File

@@ -116,7 +116,7 @@ class RecordReaderImpl implements RecordReader {
private List<MessageId> readMessageIds() throws IOException { private List<MessageId> readMessageIds() throws IOException {
if (payloadLength == 0) throw new FormatException(); if (payloadLength == 0) throw new FormatException();
if (payloadLength % UniqueId.LENGTH != 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) { for (int off = 0; off < payloadLength; off += UniqueId.LENGTH) {
byte[] id = new byte[UniqueId.LENGTH]; byte[] id = new byte[UniqueId.LENGTH];
System.arraycopy(payload, off, id, 0, UniqueId.LENGTH); System.arraycopy(payload, off, id, 0, UniqueId.LENGTH);

View File

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.RecordWriter; import org.briarproject.bramble.api.sync.RecordWriter;
@@ -28,7 +28,6 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH; 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 = private static final Logger LOG =
Logger.getLogger(SimplexOutgoingSession.class.getName()); 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 DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
@@ -67,7 +71,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.recordWriter = recordWriter; this.recordWriter = recordWriter;
outstandingQueries = new AtomicInteger(2); // One per type of record outstandingQueries = new AtomicInteger(2); // One per type of record
writerTasks = new LinkedBlockingQueue<>(); writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
} }
@IoExecutor @IoExecutor
@@ -110,9 +114,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) { if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof LifecycleEvent) { } else if (e instanceof ShutdownEvent) {
LifecycleEvent l = (LifecycleEvent) e; interrupt();
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }

View File

@@ -64,8 +64,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.validationExecutor = validationExecutor; this.validationExecutor = validationExecutor;
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
validators = new ConcurrentHashMap<>(); validators = new ConcurrentHashMap<ClientId, MessageValidator>();
hooks = new ConcurrentHashMap<>(); hooks = new ConcurrentHashMap<ClientId, IncomingMessageHook>();
} }
@Override @Override
@@ -93,14 +93,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
hooks.put(c, hook); hooks.put(c, hook);
} }
private void validateOutstandingMessagesAsync(ClientId c) { private void validateOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(() -> validateOutstandingMessages(c)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
validateOutstandingMessages(c);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
private void validateOutstandingMessages(ClientId c) { private void validateOutstandingMessages(ClientId c) {
try { try {
Queue<MessageId> unvalidated = new LinkedList<>(); Queue<MessageId> unvalidated = new LinkedList<MessageId>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
unvalidated.addAll(db.getMessagesToValidate(txn, c)); 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; if (unvalidated.isEmpty()) return;
dbExecutor.execute(() -> validateNextMessage(unvalidated)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
validateNextMessage(unvalidated);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
@@ -148,14 +158,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
} }
private void deliverOutstandingMessagesAsync(ClientId c) { private void deliverOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(() -> deliverOutstandingMessages(c)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
deliverOutstandingMessages(c);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
private void deliverOutstandingMessages(ClientId c) { private void deliverOutstandingMessages(ClientId c) {
try { try {
Queue<MessageId> pending = new LinkedList<>(); Queue<MessageId> pending = new LinkedList<MessageId>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
pending.addAll(db.getPendingMessages(txn, c)); 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; if (pending.isEmpty()) return;
dbExecutor.execute(() -> deliverNextPendingMessage(pending)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
deliverNextPendingMessage(pending);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
@@ -208,7 +229,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
pending.addAll(getPendingDependents(txn, id)); pending.addAll(getPendingDependents(txn, id));
if (result.share) { if (result.share) {
db.setMessageShared(txn, id); db.setMessageShared(txn, id);
toShare = new LinkedList<>(states.keySet()); toShare = new LinkedList<MessageId>(
states.keySet());
} }
} else { } else {
invalidate = getDependentsToInvalidate(txn, id); invalidate = getDependentsToInvalidate(txn, id);
@@ -233,8 +255,13 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
} }
private void validateMessageAsync(Message m, Group g) { private void validateMessageAsync(final Message m, final Group g) {
validationExecutor.execute(() -> validateMessage(m, g)); validationExecutor.execute(new Runnable() {
@Override
public void run() {
validateMessage(m, g);
}
});
} }
@ValidationExecutor @ValidationExecutor
@@ -250,16 +277,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
} catch (InvalidMessageException e) { } catch (InvalidMessageException e) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e); LOG.log(INFO, e.toString(), e);
Queue<MessageId> invalidate = new LinkedList<>(); Queue<MessageId> invalidate = new LinkedList<MessageId>();
invalidate.add(m.getId()); invalidate.add(m.getId());
invalidateNextMessageAsync(invalidate); invalidateNextMessageAsync(invalidate);
} }
} }
} }
private void storeMessageContextAsync(Message m, ClientId c, private void storeMessageContextAsync(final Message m, final ClientId c,
MessageContext result) { final MessageContext result) {
dbExecutor.execute(() -> storeMessageContext(m, c, result)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
storeMessageContext(m, c, result);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
@@ -299,7 +331,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
pending = getPendingDependents(txn, id); pending = getPendingDependents(txn, id);
if (result.share) { if (result.share) {
db.setMessageShared(txn, id); db.setMessageShared(txn, id);
toShare = new LinkedList<>(dependencies); toShare =
new LinkedList<MessageId>(dependencies);
} }
} else { } else {
invalidate = getDependentsToInvalidate(txn, id); invalidate = getDependentsToInvalidate(txn, id);
@@ -345,7 +378,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
@DatabaseExecutor @DatabaseExecutor
private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m) private Queue<MessageId> getPendingDependents(Transaction txn, MessageId m)
throws DbException { throws DbException {
Queue<MessageId> pending = new LinkedList<>(); Queue<MessageId> pending = new LinkedList<MessageId>();
Map<MessageId, State> states = db.getMessageDependents(txn, m); Map<MessageId, State> states = db.getMessageDependents(txn, m);
for (Entry<MessageId, State> e : states.entrySet()) { for (Entry<MessageId, State> e : states.entrySet()) {
if (e.getValue() == PENDING) pending.add(e.getKey()); if (e.getValue() == PENDING) pending.add(e.getKey());
@@ -353,14 +386,19 @@ class ValidationManagerImpl implements ValidationManager, Service,
return pending; return pending;
} }
private void shareOutstandingMessagesAsync(ClientId c) { private void shareOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(() -> shareOutstandingMessages(c)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
shareOutstandingMessages(c);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
private void shareOutstandingMessages(ClientId c) { private void shareOutstandingMessages(ClientId c) {
try { try {
Queue<MessageId> toShare = new LinkedList<>(); Queue<MessageId> toShare = new LinkedList<MessageId>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
toShare.addAll(db.getMessagesToShare(txn, c)); 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 * This method should only be called for messages that have all their
* dependencies delivered and have been delivered themselves. * dependencies delivered and have been delivered themselves.
*/ */
private void shareNextMessageAsync(Queue<MessageId> toShare) { private void shareNextMessageAsync(final Queue<MessageId> toShare) {
if (toShare.isEmpty()) return; if (toShare.isEmpty()) return;
dbExecutor.execute(() -> shareNextMessage(toShare)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
shareNextMessage(toShare);
}
});
} }
@DatabaseExecutor @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; if (invalidate.isEmpty()) return;
dbExecutor.execute(() -> invalidateNextMessage(invalidate)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
invalidateNextMessage(invalidate);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
@@ -448,7 +496,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
@DatabaseExecutor @DatabaseExecutor
private Queue<MessageId> getDependentsToInvalidate(Transaction txn, private Queue<MessageId> getDependentsToInvalidate(Transaction txn,
MessageId m) throws DbException { MessageId m) throws DbException {
Queue<MessageId> invalidate = new LinkedList<>(); Queue<MessageId> invalidate = new LinkedList<MessageId>();
Map<MessageId, State> states = db.getMessageDependents(txn, m); Map<MessageId, State> states = db.getMessageDependents(txn, m);
for (Entry<MessageId, State> e : states.entrySet()) { for (Entry<MessageId, State> e : states.entrySet()) {
if (e.getValue() != INVALID) invalidate.add(e.getKey()); if (e.getValue() != INVALID) invalidate.add(e.getKey());
@@ -466,12 +514,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
} }
private void loadGroupAndValidateAsync(Message m) { private void loadGroupAndValidateAsync(final Message m) {
dbExecutor.execute(() -> loadGroupAndValidate(m)); dbExecutor.execute(new Runnable() {
@Override
public void run() {
loadGroupAndValidate(m);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
private void loadGroupAndValidate(Message m) { private void loadGroupAndValidate(final Message m) {
try { try {
Group g; Group g;
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);

View File

@@ -58,14 +58,15 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
this.pluginConfig = pluginConfig; this.pluginConfig = pluginConfig;
this.transportKeyManagerFactory = transportKeyManagerFactory; this.transportKeyManagerFactory = transportKeyManagerFactory;
// Use a ConcurrentHashMap as a thread-safe set // Use a ConcurrentHashMap as a thread-safe set
activeContacts = new ConcurrentHashMap<>(); activeContacts = new ConcurrentHashMap<ContactId, Boolean>();
managers = new ConcurrentHashMap<>(); managers = new ConcurrentHashMap<TransportId, TransportKeyManager>();
} }
@Override @Override
public void startService() throws ServiceException { public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
Map<TransportId, Integer> transports = new HashMap<>(); Map<TransportId, Integer> transports =
new HashMap<TransportId, Integer>();
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())
transports.put(f.getId(), f.getMaxLatency()); transports.put(f.getId(), f.getMaxLatency());
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) 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); activeContacts.remove(c);
dbExecutor.execute(() -> { dbExecutor.execute(new Runnable() {
for (TransportKeyManager m : managers.values()) m.removeContact(c); @Override
public void run() {
for (TransportKeyManager m : managers.values())
m.removeContact(c);
}
}); });
} }
} }

View File

@@ -45,7 +45,7 @@ class ReorderingWindow {
} }
List<Long> getUnseen() { 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++) for (int i = 0; i < seen.length; i++)
if (!seen[i]) unseen.add(base + i); if (!seen[i]) unseen.add(base + i);
return unseen; return unseen;
@@ -69,8 +69,8 @@ class ReorderingWindow {
return new Change(added, removed); return new Change(added, removed);
} }
// Record the elements that will be added and removed // Record the elements that will be added and removed
List<Long> added = new ArrayList<>(slide); List<Long> added = new ArrayList<Long>(slide);
List<Long> removed = new ArrayList<>(slide); List<Long> removed = new ArrayList<Long>(slide);
for (int i = 0; i < slide; i++) { for (int i = 0; i < slide; i++) {
if (!seen[i]) removed.add(base + i); if (!seen[i]) removed.add(base + i);
added.add(base + seen.length + i); added.add(base + seen.length + i);

View File

@@ -65,9 +65,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
this.transportId = transportId; this.transportId = transportId;
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE; rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
lock = new ReentrantLock(); lock = new ReentrantLock();
inContexts = new HashMap<>(); inContexts = new HashMap<Bytes, TagContext>();
outContexts = new HashMap<>(); outContexts = new HashMap<ContactId, MutableOutgoingKeys>();
keys = new HashMap<>(); keys = new HashMap<ContactId, MutableTransportKeys>();
} }
@Override @Override
@@ -134,22 +134,32 @@ class TransportKeyManagerImpl implements TransportKeyManager {
} }
private void scheduleKeyRotation(long now) { private void scheduleKeyRotation(long now) {
Runnable task = new Runnable() {
@Override
public void run() {
rotateKeys();
}
};
long delay = rotationPeriodLength - now % rotationPeriodLength; long delay = rotationPeriodLength - now % rotationPeriodLength;
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS); scheduler.schedule(task, delay, MILLISECONDS);
} }
private void rotateKeys() { private void rotateKeys() {
dbExecutor.execute(() -> { dbExecutor.execute(new Runnable() {
try { @Override
Transaction txn = db.startTransaction(false); public void run() {
try { try {
rotateKeys(txn); Transaction txn = db.startTransaction(false);
db.commitTransaction(txn); try {
} finally { rotateKeys(txn);
db.endTransaction(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(); lock.lock();
try { try {
// Rotate the keys to the current rotation period // 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()) for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet())
snapshot.put(e.getKey(), e.getValue().snapshot()); snapshot.put(e.getKey(), e.getValue().snapshot());
RotationResult rotationResult = rotateKeys(snapshot, now); RotationResult rotationResult = rotateKeys(snapshot, now);
@@ -300,8 +311,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private final Map<ContactId, TransportKeys> current, rotated; private final Map<ContactId, TransportKeys> current, rotated;
private RotationResult() { private RotationResult() {
current = new HashMap<>(); current = new HashMap<ContactId, TransportKeys>();
rotated = new HashMap<>(); rotated = new HashMap<ContactId, TransportKeys>();
} }
} }
} }

View File

@@ -24,13 +24,16 @@ public class PoliteExecutorTest extends BrambleTestCase {
Executor delegate = Executors.newSingleThreadExecutor(); Executor delegate = Executors.newSingleThreadExecutor();
// Allow all the tasks to be delegated straight away // Allow all the tasks to be delegated straight away
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2); PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
List<Integer> list = new Vector<>(); final List<Integer> list = new Vector<Integer>();
CountDownLatch latch = new CountDownLatch(TASKS); final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) { for (int i = 0; i < TASKS; i++) {
int result = i; final int result = i;
polite.execute(() -> { polite.execute(new Runnable() {
list.add(result); @Override
latch.countDown(); public void run() {
list.add(result);
latch.countDown();
}
}); });
} }
// Wait for all the tasks to finish // Wait for all the tasks to finish
@@ -46,13 +49,16 @@ public class PoliteExecutorTest extends BrambleTestCase {
Executor delegate = Executors.newSingleThreadExecutor(); Executor delegate = Executors.newSingleThreadExecutor();
// Allow two tasks to be delegated at a time // Allow two tasks to be delegated at a time
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 2); PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 2);
List<Integer> list = new Vector<>(); final List<Integer> list = new Vector<Integer>();
CountDownLatch latch = new CountDownLatch(TASKS); final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) { for (int i = 0; i < TASKS; i++) {
int result = i; final int result = i;
polite.execute(() -> { polite.execute(new Runnable() {
list.add(result); @Override
latch.countDown(); public void run() {
list.add(result);
latch.countDown();
}
}); });
} }
// Wait for all the tasks to finish // Wait for all the tasks to finish
@@ -67,20 +73,23 @@ public class PoliteExecutorTest extends BrambleTestCase {
Executor delegate = Executors.newCachedThreadPool(); Executor delegate = Executors.newCachedThreadPool();
// Allow all the tasks to be delegated straight away // Allow all the tasks to be delegated straight away
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2); PoliteExecutor polite = new PoliteExecutor(TAG, delegate, TASKS * 2);
List<Integer> list = new Vector<>(); final List<Integer> list = new Vector<Integer>();
CountDownLatch[] latches = new CountDownLatch[TASKS]; 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++) latches[i] = new CountDownLatch(1);
for (int i = 0; i < TASKS; i++) { for (int i = 0; i < TASKS; i++) {
int result = i; final int result = i;
polite.execute(() -> { polite.execute(new Runnable() {
try { @Override
// Each task waits for the next task, if any, to finish public void run() {
if (result < TASKS - 1) latches[result + 1].await(); try {
list.add(result); // Each task waits for the next task, if any, to finish
} catch (InterruptedException e) { if (result < TASKS - 1) latches[result + 1].await();
fail(); list.add(result);
} catch (InterruptedException e) {
fail();
}
latches[result].countDown();
} }
latches[result].countDown();
}); });
} }
// Wait for all the tasks to finish // Wait for all the tasks to finish
@@ -95,19 +104,22 @@ public class PoliteExecutorTest extends BrambleTestCase {
Executor delegate = Executors.newCachedThreadPool(); Executor delegate = Executors.newCachedThreadPool();
// Allow one task to be delegated at a time // Allow one task to be delegated at a time
PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 1); PoliteExecutor polite = new PoliteExecutor(TAG, delegate, 1);
List<Integer> list = new Vector<>(); final List<Integer> list = new Vector<Integer>();
CountDownLatch latch = new CountDownLatch(TASKS); final CountDownLatch latch = new CountDownLatch(TASKS);
for (int i = 0; i < TASKS; i++) { for (int i = 0; i < TASKS; i++) {
int result = i; final int result = i;
polite.execute(() -> { polite.execute(new Runnable() {
try { @Override
// Each task runs faster than the previous task public void run() {
Thread.sleep(TASKS - result); try {
list.add(result); // Each task runs faster than the previous task
} catch (InterruptedException e) { Thread.sleep(TASKS - result);
fail(); list.add(result);
} catch (InterruptedException e) {
fail();
}
latch.countDown();
} }
latch.countDown();
}); });
} }
// Wait for all the tasks to finish // Wait for all the tasks to finish

View File

@@ -83,9 +83,9 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
@Test(expected = InvalidMessageException.class) @Test(expected = InvalidMessageException.class)
public void testRejectsTooShortMessage() throws Exception { 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 // 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() {{ context.checking(new Expectations() {{
oneOf(invalidMessage).getTimestamp(); oneOf(invalidMessage).getTimestamp();
@@ -101,8 +101,8 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
@Test @Test
public void testAcceptsMinLengthMessage() throws Exception { public void testAcceptsMinLengthMessage() throws Exception {
byte[] shortRaw = new byte[MESSAGE_HEADER_LENGTH + 1]; final byte[] shortRaw = new byte[MESSAGE_HEADER_LENGTH + 1];
Message shortMessage = final Message shortMessage =
new Message(messageId, groupId, timestamp, shortRaw); new Message(messageId, groupId, timestamp, shortRaw);
context.checking(new Expectations() {{ context.checking(new Expectations() {{

View File

@@ -76,8 +76,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testAddLocalMessage() throws Exception { public void testAddLocalMessage() throws Exception {
boolean shared = true; final boolean shared = true;
Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
@@ -95,7 +95,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testCreateMessage() throws Exception { public void testCreateMessage() throws Exception {
byte[] bytes = expectToByteArray(list); final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(messageFactory).createMessage(groupId, timestamp, bytes); oneOf(messageFactory).createMessage(groupId, timestamp, bytes);
@@ -107,7 +107,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testGetMessageAsList() throws Exception { public void testGetMessageAsList() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
expectToList(true); expectToList(true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -125,7 +125,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testGetGroupMetadataAsDictionary() throws Exception { public void testGetGroupMetadataAsDictionary() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -145,7 +145,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testGetMessageMetadataAsDictionary() throws Exception { public void testGetMessageMetadataAsDictionary() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -165,9 +165,10 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testGetMessageMetadataAsDictionaryMap() throws Exception { public void testGetMessageMetadataAsDictionaryMap() throws Exception {
Map<MessageId, BdfDictionary> map = new HashMap<>(); final Map<MessageId, BdfDictionary> map =
new HashMap<MessageId, BdfDictionary>();
map.put(messageId, dictionary); map.put(messageId, dictionary);
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -187,13 +188,14 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testGetMessageMetadataAsDictionaryQuery() throws Exception { public void testGetMessageMetadataAsDictionaryQuery() throws Exception {
Map<MessageId, BdfDictionary> map = new HashMap<>(); final Map<MessageId, BdfDictionary> map =
new HashMap<MessageId, BdfDictionary>();
map.put(messageId, dictionary); map.put(messageId, dictionary);
BdfDictionary query = final BdfDictionary query =
BdfDictionary.of(new BdfEntry("query", "me")); BdfDictionary.of(new BdfEntry("query", "me"));
Metadata queryMetadata = new Metadata(); final Metadata queryMetadata = new Metadata();
queryMetadata.put("query", getRandomBytes(42)); queryMetadata.put("query", getRandomBytes(42));
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
@@ -215,7 +217,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testMergeGroupMetadata() throws Exception { public void testMergeGroupMetadata() throws Exception {
Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
@@ -233,7 +235,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testMergeMessageMetadata() throws Exception { public void testMergeMessageMetadata() throws Exception {
Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
@@ -280,10 +282,10 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testSign() throws Exception { public void testSign() throws Exception {
byte[] privateKey = getRandomBytes(42); final byte[] privateKey = getRandomBytes(42);
byte[] signed = getRandomBytes(42); final byte[] signed = getRandomBytes(42);
byte[] bytes = expectToByteArray(list); final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).sign(label, bytes, privateKey); oneOf(cryptoComponent).sign(label, bytes, privateKey);
will(returnValue(signed)); will(returnValue(signed));
@@ -295,8 +297,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testVerifySignature() throws Exception { public void testVerifySignature() throws Exception {
byte[] publicKey = getRandomBytes(42); final byte[] publicKey = getRandomBytes(42);
byte[] bytes = expectToByteArray(list); final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage); oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
@@ -309,8 +311,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
@Test @Test
public void testVerifyWrongSignature() throws Exception { public void testVerifyWrongSignature() throws Exception {
byte[] publicKey = getRandomBytes(42); final byte[] publicKey = getRandomBytes(42);
byte[] bytes = expectToByteArray(list); final byte[] bytes = expectToByteArray(list);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage); oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
@@ -327,8 +329,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
} }
} }
private byte[] expectToByteArray(BdfList list) throws Exception { private byte[] expectToByteArray(final BdfList list) throws Exception {
BdfWriter bdfWriter = context.mock(BdfWriter.class); final BdfWriter bdfWriter = context.mock(BdfWriter.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfWriterFactory) oneOf(bdfWriterFactory)
@@ -339,8 +341,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
return new byte[0]; return new byte[0];
} }
private void expectToList(boolean eof) throws Exception { private void expectToList(final boolean eof) throws Exception {
BdfReader bdfReader = context.mock(BdfReader.class); final BdfReader bdfReader = context.mock(BdfReader.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory) oneOf(bdfReaderFactory)

View File

@@ -46,10 +46,10 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testAddContact() throws Exception { public void testAddContact() throws Exception {
SecretKey master = getSecretKey(); final SecretKey master = getSecretKey();
long timestamp = 42; final long timestamp = 42;
boolean alice = true; final boolean alice = true;
Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
@@ -64,13 +64,14 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
}}); }});
assertEquals(contactId, contactManager.addContact(remote, local, assertEquals(contactId, contactManager
master, timestamp, alice, verified, active)); .addContact(remote, local, master, timestamp, alice, verified,
active));
} }
@Test @Test
public void testGetContact() throws Exception { public void testGetContact() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
@@ -85,8 +86,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testGetContactByAuthor() throws Exception { public void testGetContactByAuthor() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
Collection<Contact> contacts = Collections.singleton(contact); final Collection<Contact> contacts = Collections.singleton(contact);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
@@ -101,7 +102,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test(expected = NoSuchContactException.class) @Test(expected = NoSuchContactException.class)
public void testGetContactByUnknownAuthor() throws Exception { public void testGetContactByUnknownAuthor() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
@@ -115,8 +116,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test(expected = NoSuchContactException.class) @Test(expected = NoSuchContactException.class)
public void testGetContactByUnknownLocalAuthor() throws Exception { public void testGetContactByUnknownLocalAuthor() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
Collection<Contact> contacts = Collections.singleton(contact); final Collection<Contact> contacts = Collections.singleton(contact);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
@@ -131,9 +132,10 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testActiveContacts() throws Exception { public void testActiveContacts() throws Exception {
Collection<Contact> activeContacts = Collections.singletonList(contact); 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)); 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() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
@@ -148,7 +150,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testRemoveContact() throws Exception { public void testRemoveContact() throws Exception {
Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn)); will(returnValue(txn));
@@ -164,7 +166,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testSetContactActive() throws Exception { public void testSetContactActive() throws Exception {
Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).setContactActive(txn, contactId, active); oneOf(db).setContactActive(txn, contactId, active);
}}); }});
@@ -174,7 +176,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testContactExists() throws Exception { public void testContactExists() throws Exception {
Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));

View File

@@ -69,7 +69,7 @@ public class EllipticCurvePerformanceTest {
ECPublicKeyParameters public2 = ECPublicKeyParameters public2 =
(ECPublicKeyParameters) keyPair2.getPublic(); (ECPublicKeyParameters) keyPair2.getPublic();
// Time some ECDH key agreements // Time some ECDH key agreements
List<Long> samples = new ArrayList<>(); List<Long> samples = new ArrayList<Long>();
for (int i = 0; i < SAMPLES; i++) { for (int i = 0; i < SAMPLES; i++) {
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement(); ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
long start = System.nanoTime(); long start = System.nanoTime();
@@ -79,7 +79,7 @@ public class EllipticCurvePerformanceTest {
} }
long agreementMedian = median(samples); long agreementMedian = median(samples);
// Time some signatures // Time some signatures
List<byte[]> signatures = new ArrayList<>(); List<byte[]> signatures = new ArrayList<byte[]>();
samples.clear(); samples.clear();
for (int i = 0; i < SAMPLES; i++) { for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2sDigest(); Digest digest = new Blake2sDigest();

View File

@@ -145,7 +145,7 @@ public class KeyDerivationTest extends BrambleTestCase {
} }
private void assertAllDifferent(TransportKeys... transportKeys) { private void assertAllDifferent(TransportKeys... transportKeys) {
List<SecretKey> secretKeys = new ArrayList<>(); List<SecretKey> secretKeys = new ArrayList<SecretKey>();
for (TransportKeys k : transportKeys) { for (TransportKeys k : transportKeys) {
secretKeys.add(k.getPreviousIncomingKeys().getTagKey()); secretKeys.add(k.getPreviousIncomingKeys().getTagKey());
secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey()); secretKeys.add(k.getPreviousIncomingKeys().getHeaderKey());
@@ -160,7 +160,7 @@ public class KeyDerivationTest extends BrambleTestCase {
} }
private void assertAllDifferent(List<SecretKey> keys) { 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()))); for (SecretKey k : keys) assertTrue(set.add(new Bytes(k.getBytes())));
} }
} }

View File

@@ -28,7 +28,7 @@ public class TagEncodingTest extends BrambleTestCase {
@Test @Test
public void testKeyAffectsTag() throws Exception { public void testKeyAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<>(); Set<Bytes> set = new HashSet<Bytes>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
SecretKey tagKey = TestUtils.getSecretKey(); SecretKey tagKey = TestUtils.getSecretKey();
@@ -39,7 +39,7 @@ public class TagEncodingTest extends BrambleTestCase {
@Test @Test
public void testProtocolVersionAffectsTag() throws Exception { public void testProtocolVersionAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<>(); Set<Bytes> set = new HashSet<Bytes>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber); crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
@@ -49,7 +49,7 @@ public class TagEncodingTest extends BrambleTestCase {
@Test @Test
public void testStreamNumberAffectsTag() throws Exception { public void testStreamNumberAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<>(); Set<Bytes> set = new HashSet<Bytes>();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i); crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.data; package org.briarproject.bramble.data;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -154,7 +155,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
@Test @Test
public void testWriteList() throws IOException { 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); for (int i = 0; i < 3; i++) l.add(i);
w.writeList(l); w.writeList(l);
// LIST tag, elements as integers, END tag // LIST tag, elements as integers, END tag
@@ -163,7 +164,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
@Test @Test
public void testListCanContainNull() throws IOException { public void testListCanContainNull() throws IOException {
List<Object> l = new ArrayList<>(); List<Object> l = new ArrayList<Object>();
l.add(1); l.add(1);
l.add(null); l.add(null);
l.add(NULL_VALUE); l.add(NULL_VALUE);
@@ -176,7 +177,7 @@ public class BdfWriterImplTest extends BrambleTestCase {
@Test @Test
public void testWriteDictionary() throws IOException { public void testWriteDictionary() throws IOException {
// Use LinkedHashMap to get predictable iteration order // 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); for (int i = 0; i < 4; i++) m.put(String.valueOf(i), i);
w.writeDictionary(m); w.writeDictionary(m);
// DICTIONARY tag, keys as strings and values as integers, END tag // DICTIONARY tag, keys as strings and values as integers, END tag
@@ -215,12 +216,12 @@ public class BdfWriterImplTest extends BrambleTestCase {
@Test @Test
public void testWriteNestedDictionariesAndLists() throws IOException { 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]); inner.put("bar", new byte[0]);
List<Object> list = new ArrayList<>(); List<Object> list = new ArrayList<Object>();
list.add(1); list.add(1);
list.add(inner); list.add(inner);
Map<String, Object> outer = new LinkedHashMap<>(); Map<String, Object> outer = new LinkedHashMap<String, Object>();
outer.put("foo", list); outer.put("foo", list);
w.writeDictionary(outer); w.writeDictionary(outer);
// DICTIONARY tag, "foo" as string, LIST tag, 1 as integer, // DICTIONARY tag, "foo" as string, LIST tag, 1 as integer,

View File

@@ -99,7 +99,7 @@ public class MetadataEncoderParserIntegrationTest extends BrambleTestCase {
@Test @Test
public void testList() throws FormatException { public void testList() throws FormatException {
List<Long> l = new ArrayList<>(4); List<Long> l = new ArrayList<Long>(4);
l.add(42L); l.add(42L);
l.add(1337L); l.add(1337L);
l.add(Long.MIN_VALUE); l.add(Long.MIN_VALUE);
@@ -114,7 +114,7 @@ public class MetadataEncoderParserIntegrationTest extends BrambleTestCase {
@Test @Test
public void testDictionary() throws FormatException { 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("1", true);
m.put("2", false); m.put("2", false);
@@ -130,19 +130,19 @@ public class MetadataEncoderParserIntegrationTest extends BrambleTestCase {
@Test @Test
public void testComplexDictionary() throws FormatException { public void testComplexDictionary() throws FormatException {
Map<String, List> m = new HashMap<>(); Map<String, List> m = new HashMap<String, List>();
List<String> one = new ArrayList<>(3); List<String> one = new ArrayList<String>(3);
one.add("\uFDD0"); one.add("\uFDD0");
one.add("\uFDD1"); one.add("\uFDD1");
one.add("\uFDD2"); one.add("\uFDD2");
m.put("One", one); m.put("One", one);
List<String> two = new ArrayList<>(2); List<String> two = new ArrayList<String>(2);
two.add("\u0080"); two.add("\u0080");
two.add("\uD800\uDC00"); two.add("\uD800\uDC00");
m.put("Two", two); m.put("Two", two);
d.put("test", m); d.put("test", m);
Map<String, Boolean> m2 = new HashMap<>(); Map<String, Boolean> m2 = new HashMap<String, Boolean>();
m2.put("should be true", true); m2.put("should be true", true);
d.put("another test", m2); d.put("another test", m2);

View File

@@ -323,7 +323,7 @@ public class BasicH2Test extends BrambleTestCase {
private List<String> getNames() throws SQLException { private List<String> getNames() throws SQLException {
String sql = "SELECT name FROM foo ORDER BY uniqueId"; String sql = "SELECT name FROM foo ORDER BY uniqueId";
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<String>();
try { try {
PreparedStatement ps = connection.prepareStatement(sql); PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery(); ResultSet rs = ps.executeQuery();

View File

@@ -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.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys; 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.test.TestUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList; 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.assertNotNull;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
public class DatabaseComponentImplTest extends BrambleMockTestCase { public class DatabaseComponentImplTest extends BrambleTestCase {
@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);
private final Object txn = new Object(); private final Object txn = new Object();
private final ClientId clientId; private final ClientId clientId;
@@ -125,16 +120,21 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private DatabaseComponent createDatabaseComponent(Database<Object> database, private DatabaseComponent createDatabaseComponent(Database<Object> database,
EventBus eventBus, ShutdownManager shutdown) { EventBus eventBus, ShutdownManager shutdown) {
return new DatabaseComponentImpl<>(database, Object.class, eventBus, return new DatabaseComponentImpl<Object>(database, Object.class,
shutdown); eventBus, shutdown);
} }
@Test @Test
@SuppressWarnings("unchecked")
public void testSimpleCalls() throws Exception { 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() {{ context.checking(new Expectations() {{
// open() // open()
oneOf(database).open(null); oneOf(database).open();
will(returnValue(false)); will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
@@ -194,12 +194,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// endTransaction() // endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// close() // close()
oneOf(shutdown).removeShutdownHook(shutdownHandle);
oneOf(database).close(); oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertFalse(db.open(null)); assertFalse(db.open());
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalAuthor(transaction, localAuthor); db.addLocalAuthor(transaction, localAuthor);
@@ -220,11 +221,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
db.close(); db.close();
context.assertIsSatisfied();
} }
@Test @Test
public void testLocalMessagesAreNotStoredUnlessGroupExists() public void testLocalMessagesAreNotStoredUnlessGroupExists()
throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -244,10 +252,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testAddLocalMessage() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -279,11 +294,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testVariousMethodsThrowExceptionIfContactIsMissing() public void testVariousMethodsThrowExceptionIfContactIsMissing()
throws Exception { 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() {{ context.checking(new Expectations() {{
// Check whether the contact is in the DB (which it's not) // Check whether the contact is in the DB (which it's not)
exactly(18).of(database).startTransaction(); exactly(18).of(database).startTransaction();
@@ -478,11 +500,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing() public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing()
throws Exception { 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() {{ context.checking(new Expectations() {{
// Check whether the pseudonym is in the DB (which it's not) // Check whether the pseudonym is in the DB (which it's not)
exactly(3).of(database).startTransaction(); exactly(3).of(database).startTransaction();
@@ -523,11 +552,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testVariousMethodsThrowExceptionIfGroupIsMissing() public void testVariousMethodsThrowExceptionIfGroupIsMissing()
throws Exception { 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() {{ context.checking(new Expectations() {{
// Check whether the group is in the DB (which it's not) // Check whether the group is in the DB (which it's not)
exactly(8).of(database).startTransaction(); exactly(8).of(database).startTransaction();
@@ -621,11 +657,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testVariousMethodsThrowExceptionIfMessageIsMissing() public void testVariousMethodsThrowExceptionIfMessageIsMissing()
throws Exception { 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() {{ context.checking(new Expectations() {{
// Check whether the message is in the DB (which it's not) // Check whether the message is in the DB (which it's not)
exactly(11).of(database).startTransaction(); exactly(11).of(database).startTransaction();
@@ -749,11 +792,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testVariousMethodsThrowExceptionIfTransportIsMissing() public void testVariousMethodsThrowExceptionIfTransportIsMissing()
throws Exception { 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() {{ context.checking(new Expectations() {{
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -840,12 +890,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testGenerateAck() throws Exception { public void testGenerateAck() throws Exception {
Collection<MessageId> messagesToAck = Arrays.asList(messageId, final Collection<MessageId> messagesToAck = Arrays.asList(messageId,
messageId1); 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -868,13 +925,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testGenerateBatch() throws Exception { public void testGenerateBatch() throws Exception {
byte[] raw1 = new byte[size]; final byte[] raw1 = new byte[size];
Collection<MessageId> ids = Arrays.asList(messageId, messageId1); final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
Collection<byte[]> messages = Arrays.asList(raw, raw1); 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -905,12 +969,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testGenerateOffer() throws Exception { public void testGenerateOffer() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Collection<MessageId> ids = Arrays.asList(messageId, messageId1); 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -936,12 +1007,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testGenerateRequest() throws Exception { public void testGenerateRequest() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Collection<MessageId> ids = Arrays.asList(messageId, messageId1); 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -964,13 +1042,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testGenerateRequestedBatch() throws Exception { public void testGenerateRequestedBatch() throws Exception {
byte[] raw1 = new byte[size]; final byte[] raw1 = new byte[size];
Collection<MessageId> ids = Arrays.asList(messageId, messageId1); final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
Collection<byte[]> messages = Arrays.asList(raw, raw1); 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1002,10 +1087,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testReceiveAck() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1028,10 +1120,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testReceiveMessage() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1076,10 +1175,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testReceiveDuplicateMessage() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1106,10 +1212,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testReceiveMessageWithoutVisibleGroup() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1129,13 +1242,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testReceiveOffer() throws Exception { public void testReceiveOffer() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
MessageId messageId2 = new MessageId(TestUtils.getRandomId()); final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
MessageId messageId3 = 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1176,10 +1296,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testReceiveRequest() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1203,10 +1330,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testChangingVisibilityCallsListeners() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1236,11 +1370,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testNotChangingVisibilityDoesNotCallListeners() public void testNotChangingVisibilityDoesNotCallListeners()
throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1262,13 +1403,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys(); final TransportKeys transportKeys = createTransportKeys();
Map<ContactId, TransportKeys> keys = Collections.singletonMap( final Map<ContactId, TransportKeys> keys = Collections.singletonMap(
contactId, transportKeys); 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() {{ context.checking(new Expectations() {{
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -1298,6 +1446,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
private TransportKeys createTransportKeys() { private TransportKeys createTransportKeys() {
@@ -1322,14 +1472,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testMergeSettings() throws Exception { public void testMergeSettings() throws Exception {
Settings before = new Settings(); final Settings before = new Settings();
before.put("foo", "bar"); before.put("foo", "bar");
before.put("baz", "bam"); before.put("baz", "bam");
Settings update = new Settings(); final Settings update = new Settings();
update.put("baz", "qux"); update.put("baz", "qux");
Settings merged = new Settings(); final Settings merged = new Settings();
merged.put("foo", "bar"); merged.put("foo", "bar");
merged.put("baz", "qux"); 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() {{ context.checking(new Expectations() {{
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -1359,6 +1514,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
@@ -1388,6 +1545,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private void testCannotStartTransactionDuringTransaction( private void testCannotStartTransactionDuringTransaction(
boolean firstTxnReadOnly, boolean secondTxnReadOnly) boolean firstTxnReadOnly, boolean secondTxnReadOnly)
throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1397,12 +1560,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
shutdown); shutdown);
assertNotNull(db.startTransaction(firstTxnReadOnly)); assertNotNull(db.startTransaction(firstTxnReadOnly));
db.startTransaction(secondTxnReadOnly); try {
fail(); db.startTransaction(secondTxnReadOnly);
fail();
} finally {
context.assertIsSatisfied();
}
} }
@Test @Test
public void testCannotAddLocalIdentityAsContact() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1426,10 +1599,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
public void testCannotAddDuplicateContact() throws Exception { 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() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1455,16 +1636,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
context.assertIsSatisfied();
} }
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testMessageDependencies() throws Exception { public void testMessageDependencies() throws Exception {
int shutdownHandle = 12345; final int shutdownHandle = 12345;
MessageId messageId2 = new MessageId(TestUtils.getRandomId()); 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() {{ context.checking(new Expectations() {{
// open() // open()
oneOf(database).open(null); oneOf(database).open();
will(returnValue(false)); will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
@@ -1506,16 +1693,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// endTransaction() // endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// close() // close()
oneOf(shutdown).removeShutdownHook(shutdownHandle);
oneOf(database).close(); oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertFalse(db.open(null)); assertFalse(db.open());
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalMessage(transaction, message, metadata, true); db.addLocalMessage(transaction, message, metadata, true);
Collection<MessageId> dependencies = new ArrayList<>(2); Collection<MessageId> dependencies = new ArrayList<MessageId>(2);
dependencies.add(messageId1); dependencies.add(messageId1);
dependencies.add(messageId2); dependencies.add(messageId2);
db.addMessageDependencies(transaction, message, dependencies); db.addMessageDependencies(transaction, message, dependencies);
@@ -1526,5 +1714,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
db.close(); db.close();
context.assertIsSatisfied();
} }
} }

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