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
412 changed files with 9211 additions and 14975 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 1620 versionCode 1611
versionName "0.16.20" 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,
scheduler, 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

@@ -5,84 +5,37 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
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.Nullable; import javax.annotation.Nullable;
import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE;
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.net.wifi.WifiManager.EXTRA_WIFI_STATE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.SECONDS;
@NotNullByDefault @NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin { class AndroidLanTcpPlugin extends LanTcpPlugin {
// See android.net.wifi.WifiManager
private static final String WIFI_AP_STATE_CHANGED_ACTION =
"android.net.wifi.WIFI_AP_STATE_CHANGED";
private static final int WIFI_AP_STATE_ENABLED = 13;
private static final byte[] WIFI_AP_ADDRESS_BYTES =
{(byte) 192, (byte) 168, 43, 1};
private static final InetAddress WIFI_AP_ADDRESS;
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidLanTcpPlugin.class.getName()); Logger.getLogger(AndroidLanTcpPlugin.class.getName());
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final ConnectivityManager connectivityManager;
@Nullable
private final WifiManager wifiManager;
@Nullable @Nullable
private volatile BroadcastReceiver networkStateReceiver = null; private volatile BroadcastReceiver networkStateReceiver = null;
private volatile SocketFactory socketFactory;
AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler, AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff,
Backoff backoff, Context appContext, DuplexPluginCallback callback, Context appContext, DuplexPluginCallback callback, int maxLatency,
int maxLatency, int maxIdleTime) { int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime); super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
ConnectivityManager connectivityManager = (ConnectivityManager)
appContext.getSystemService(CONNECTIVITY_SERVICE);
if (connectivityManager == null) throw new AssertionError();
this.connectivityManager = connectivityManager;
wifiManager = (WifiManager) appContext.getApplicationContext()
.getSystemService(WIFI_SERVICE);
socketFactory = SocketFactory.getDefault();
} }
@Override @Override
@@ -91,9 +44,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
running = true; running = true;
// 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(WIFI_AP_STATE_CHANGED_ACTION);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
} }
@@ -105,92 +56,21 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
tryToClose(socket); tryToClose(socket);
} }
@Override
protected Socket createSocket() throws IOException {
return socketFactory.createSocket();
}
@Override
protected Collection<InetAddress> getLocalIpAddresses() {
// If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList();
// If we're connected to a wifi network, use that network
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0)
return singletonList(intToInetAddress(info.getIpAddress()));
// If we're running an access point, return its address
if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
return singletonList(WIFI_AP_ADDRESS);
// No suitable addresses
return emptyList();
}
private InetAddress intToInetAddress(int ip) {
byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF);
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
try {
return InetAddress.getByAddress(ipBytes);
} catch (UnknownHostException e) {
// Should only be thrown if address has illegal length
throw new AssertionError(e);
}
}
// On API 21 and later, a socket that is not created with the wifi
// network's socket factory may try to connect via another network
private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault();
for (Network net : connectivityManager.getAllNetworks()) {
NetworkInfo info = connectivityManager.getNetworkInfo(net);
if (info != null && info.getType() == TYPE_WIFI)
return net.getSocketFactory();
}
LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault();
}
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;
if (isApEnabledEvent(i)) { Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
// The state change may be broadcast before the AP address is ConnectivityManager cm = (ConnectivityManager) o;
// visible, so delay handling the event NetworkInfo net = cm.getActiveNetworkInfo();
scheduler.schedule(this::handleConnectivityChange, 1, SECONDS); if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) {
} else { LOG.info("Connected to Wi-Fi");
handleConnectivityChange();
}
}
private void handleConnectivityChange() {
if (!running) return;
Collection<InetAddress> addrs = getLocalIpAddresses();
if (addrs.contains(WIFI_AP_ADDRESS)) {
LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to
// make outgoing connections on API 21+ if another network
// has internet access
socketFactory = SocketFactory.getDefault();
if (socket == null || socket.isClosed()) bind(); if (socket == null || socket.isClosed()) bind();
} else if (addrs.isEmpty()) { } else {
LOG.info("Not connected to wifi"); LOG.info("Not connected to Wi-Fi");
socketFactory = SocketFactory.getDefault();
tryToClose(socket); tryToClose(socket);
} else {
LOG.info("Connected to wifi");
socketFactory = getSocketFactory();
if (socket == null || socket.isClosed()) bind();
} }
} }
private boolean isApEnabledEvent(Intent i) {
return WIFI_AP_STATE_CHANGED_ACTION.equals(i.getAction()) &&
i.getIntExtra(EXTRA_WIFI_STATE, 0) == WIFI_AP_STATE_ENABLED;
}
} }
} }

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -28,15 +27,12 @@ public class AndroidLanTcpPluginFactory 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 BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
private final Context appContext; private final Context appContext;
public AndroidLanTcpPluginFactory(Executor ioExecutor, public AndroidLanTcpPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler, BackoffFactory backoffFactory, BackoffFactory backoffFactory, Context appContext) {
Context appContext) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
this.appContext = appContext; this.appContext = appContext;
} }
@@ -55,7 +51,7 @@ public class AndroidLanTcpPluginFactory 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);
return new AndroidLanTcpPlugin(ioExecutor, scheduler, backoff, return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext,
appContext, callback, MAX_LATENCY, MAX_IDLE_TIME); callback, MAX_LATENCY, MAX_IDLE_TIME);
} }
} }

View File

@@ -16,7 +16,6 @@ import android.os.PowerManager;
import net.freehaven.tor.control.EventHandler; import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection; import net.freehaven.tor.control.TorControlConnection;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
@@ -60,10 +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.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;
@@ -74,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;
@@ -110,8 +101,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(TorPlugin.class.getName()); Logger.getLogger(TorPlugin.class.getName());
private final Executor ioExecutor, connectionStatusExecutor; 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;
@@ -124,8 +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 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;
@@ -134,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;
@@ -165,9 +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);
// Don't execute more than one connection status check at a time
connectionStatusExecutor = new PoliteExecutor("TorPlugin",
ioExecutor, 1);
} }
@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,65 +691,60 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
connectionStatusExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
if (!running) return; @Override
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE); public void run() {
ConnectivityManager cm = (ConnectivityManager) o; if (!running) return;
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)) { Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
LOG.info("Online: " + online + ", wifi: " + wifi); ConnectivityManager cm = (ConnectivityManager) o;
if ("".equals(country)) LOG.info("Country code unknown"); NetworkInfo net = cm.getActiveNetworkInfo();
else LOG.info("Country code: " + country); 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);
try { if (LOG.isLoggable(INFO)) {
if (!online) { LOG.info("Online: " + online + ", wifi: " + wifi);
LOG.info("Disabling network, device is offline"); if ("".equals(country)) LOG.info("Country code unknown");
enableNetwork(false); else LOG.info("Country code: " + country);
} else if (blocked) { }
LOG.info("Disabling network, country is blocked");
enableNetwork(false); try {
} else if (network == PREF_TOR_NETWORK_NEVER if (!online) {
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) { LOG.info("Disabling network, device is offline");
LOG.info("Disabling network due to data setting"); enableNetwork(false);
enableNetwork(false); } else if (blocked) {
} else { LOG.info("Disabling network, country is blocked");
LOG.info("Enabling network"); enableNetwork(false);
enableNetwork(true); } 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);
} }
} 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;
@@ -259,30 +253,31 @@ public interface DatabaseComponent {
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException; Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated by the given
* client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToValidate(Transaction txn) Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that are pending delivery due to * Returns the IDs of any messages that are valid but pending delivery due
* dependencies on other messages. * to dependencies on other messages for the given client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getPendingMessages(Transaction txn) Collection<MessageId> getPendingMessages(Transaction txn, ClientId c)
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that have shared dependents but have * Returns the IDs of any messages from the given client
* not yet been shared themselves. * that have a shared dependent, but are still not shared themselves.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToShare(Transaction txn) Collection<MessageId> getMessagesToShare(Transaction txn,
throws DbException; ClientId 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
@@ -377,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/>
@@ -467,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
@@ -97,12 +89,9 @@ interface Database<T> {
/** /**
* Stores a message. * Stores a message.
*
* @param sender the contact from whom the message was received, or null
* if the message was created locally.
*/ */
void addMessage(T txn, Message m, State state, boolean shared, void addMessage(T txn, Message m, State state, boolean shared)
@Nullable ContactId sender) throws DbException; throws DbException;
/** /**
* Adds a dependency between two messages in the given group. * Adds a dependency between two messages in the given group.
@@ -115,6 +104,16 @@ interface Database<T> {
*/ */
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
/**
* Initialises the status of the given message with respect to the given
* contact.
*
* @param ack whether the message needs to be acknowledged.
* @param seen whether the contact has seen the message.
*/
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
throws DbException;
/** /**
* Stores a transport. * Stores a transport.
*/ */
@@ -273,7 +272,7 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g) Collection<ContactId> getGroupVisibility(T txn, GroupId g)
throws DbException; throws DbException;
/** /**
@@ -424,37 +423,31 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated by the given
* client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToValidate(T txn) throws DbException; Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
throws DbException;
/** /**
* Returns the IDs of any messages that are pending delivery due to * Returns the IDs of any messages that are still pending due to
* dependencies on other messages. * dependencies to other messages for the given client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getPendingMessages(T txn) throws DbException; Collection<MessageId> getPendingMessages(T txn, ClientId c)
throws DbException;
/** /**
* Returns the IDs of any messages that have a shared dependent but have * Returns the IDs of any messages from the given client
* not yet been shared themselves. * that have a shared dependent, but are still not shared themselves.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToShare(T txn) throws DbException; Collection<MessageId> getMessagesToShare(T txn, ClientId c)
throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
* if no messages are scheduled to be sent.
* <p/>
* Read-only.
*/
long getNextSendTime(T txn, ContactId c) throws DbException;
/** /**
* Returns the message with the given ID, in serialised form, or null if * Returns the message with the given ID, in serialised form, or null if
@@ -573,6 +566,13 @@ interface Database<T> {
*/ */
void removeMessage(T txn, MessageId m) throws DbException; void removeMessage(T txn, MessageId m) throws DbException;
/**
* Removes an offered message that was offered by the given contact, or
* returns false if there is no such message.
*/
boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
throws DbException;
/** /**
* Removes the given offered messages that were offered by the given * Removes the given offered messages that were offered by the given
* contact. * contact.
@@ -580,6 +580,12 @@ interface Database<T> {
void removeOfferedMessages(T txn, ContactId c, void removeOfferedMessages(T txn, ContactId c,
Collection<MessageId> requested) throws DbException; Collection<MessageId> requested) throws DbException;
/**
* Removes the status of the given message with respect to the given
* contact.
*/
void removeStatus(T txn, ContactId c, 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

@@ -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;
@@ -215,7 +225,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsGroup(txn, m.getGroupId())) if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) { if (!db.containsMessage(txn, m.getId())) {
db.addMessage(txn, m, DELIVERED, shared, null); addMessage(txn, m, DELIVERED, shared, null);
transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
@@ -224,6 +234,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.mergeMessageMetadata(txn, m.getId(), meta); db.mergeMessageMetadata(txn, m.getId(), meta);
} }
private void addMessage(T txn, Message m, State state, boolean shared,
@Nullable ContactId sender) throws DbException {
db.addMessage(txn, m, state, shared);
for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) {
boolean offered = db.removeOfferedMessage(txn, c, m.getId());
boolean seen = offered || (sender != null && c.equals(sender));
db.addStatus(txn, c, m.getId(), seen, seen);
}
}
@Override @Override
public void addTransport(Transaction transaction, TransportId t, public void addTransport(Transaction transaction, TransportId t,
int maxLatency) throws DbException { int maxLatency) throws DbException {
@@ -311,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);
@@ -361,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);
@@ -455,24 +475,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction) public Collection<MessageId> getMessagesToValidate(Transaction transaction,
throws DbException { ClientId c) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getMessagesToValidate(txn); return db.getMessagesToValidate(txn, c);
} }
@Override @Override
public Collection<MessageId> getPendingMessages(Transaction transaction) public Collection<MessageId> getPendingMessages(Transaction transaction,
throws DbException { ClientId c) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getPendingMessages(txn); return db.getPendingMessages(txn, c);
} }
@Override @Override
public Collection<MessageId> getMessagesToShare(Transaction transaction) public Collection<MessageId> getMessagesToShare(
throws DbException { Transaction transaction, ClientId c) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getMessagesToShare(txn); return db.getMessagesToShare(txn, c);
} }
@Nullable @Nullable
@@ -571,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 {
@@ -648,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);
@@ -672,7 +685,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId()); db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId()); db.raiseAckFlag(txn, c, m.getId());
} else { } else {
db.addMessage(txn, m, UNKNOWN, false, c); addMessage(txn, m, UNKNOWN, false, c);
transaction.attach(new MessageAddedEvent(m, c)); transaction.attach(new MessageAddedEvent(m, c));
} }
transaction.attach(new MessageToAckEvent(c)); transaction.attach(new MessageToAckEvent(c));
@@ -740,8 +753,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
GroupId id = g.getId(); GroupId id = g.getId();
if (!db.containsGroup(txn, id)) if (!db.containsGroup(txn, id))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
Collection<ContactId> affected = Collection<ContactId> affected = db.getGroupVisibility(txn, id);
db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id); db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g)); transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
@@ -758,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 {
@@ -811,9 +813,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchGroupException(); throw new NoSuchGroupException();
Visibility old = db.getGroupVisibility(txn, c, g); Visibility old = db.getGroupVisibility(txn, c, g);
if (old == v) return; if (old == v) return;
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED); if (old == INVISIBLE) {
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g); db.addGroupVisibility(txn, c, g, v == SHARED);
else db.setGroupVisibility(txn, c, g, v == SHARED); for (MessageId m : db.getMessageIds(txn, g)) {
boolean seen = db.removeOfferedMessage(txn, c, m);
db.addStatus(txn, c, m, seen, seen);
}
} else if (v == INVISIBLE) {
db.removeGroupVisibility(txn, c, g);
for (MessageId m : db.getMessageIds(txn, g))
db.removeStatus(txn, c, m);
} else {
db.setGroupVisibility(txn, c, g, v == SHARED);
}
List<ContactId> affected = Collections.singletonList(c); List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
} }
@@ -874,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

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

@@ -1,84 +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 Migration31_32 implements Migration<Connection> {
private static final Logger LOG =
Logger.getLogger(Migration31_32.class.getName());
@Override
public int getStartVersion() {
return 31;
}
@Override
public int getEndVersion() {
return 32;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
// Add denormalised columns
s.execute("ALTER TABLE statuses ADD COLUMN"
+ " (groupId BINARY(32),"
+ " timestamp BIGINT,"
+ " length INT,"
+ " state INT,"
+ " groupShared BOOLEAN,"
+ " messageShared BOOLEAN,"
+ " deleted BOOLEAN)");
// Populate columns from messages table
s.execute("UPDATE statuses AS s SET (groupId, timestamp, length,"
+ " state, messageShared, deleted) ="
+ " (SELECT groupId, timestamp, length, state, shared,"
+ " raw IS NULL FROM messages AS m"
+ " WHERE s.messageId = m.messageId)");
// Populate column from groupVisibilities table
s.execute("UPDATE statuses AS s SET groupShared ="
+ " (SELECT shared FROM groupVisibilities AS gv"
+ " WHERE s.contactId = gv.contactId"
+ " AND s.groupId = gv.groupId)");
// Add not null constraints now columns have been populated
s.execute("ALTER TABLE statuses ALTER COLUMN groupId SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN timestamp"
+ " SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN length SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN state SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN groupShared"
+ " SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN messageShared"
+ " SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN deleted SET NOT NULL");
// Add foreign key constraint
s.execute("ALTER TABLE statuses"
+ " ADD CONSTRAINT statusesForeignKeyGroupId"
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE");
} 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 {
@@ -241,11 +218,10 @@ class LanTcpPlugin extends TcpPlugin {
} }
return null; return null;
} }
Socket s = new Socket();
try { try {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.connect(remote); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -283,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
@@ -299,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

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
@@ -48,7 +47,7 @@ abstract class TcpPlugin implements DuplexPlugin {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(TcpPlugin.class.getName()); Logger.getLogger(TcpPlugin.class.getName());
protected final Executor ioExecutor, bindExecutor; protected final Executor ioExecutor;
protected final Backoff backoff; protected final Backoff backoff;
protected final DuplexPluginCallback callback; protected final DuplexPluginCallback callback;
protected final int maxLatency, maxIdleTime, socketTimeout; protected final int maxLatency, maxIdleTime, socketTimeout;
@@ -91,8 +90,6 @@ abstract class TcpPlugin implements DuplexPlugin {
if (maxIdleTime > Integer.MAX_VALUE / 2) if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE; socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2; else socketTimeout = maxIdleTime * 2;
// Don't execute more than one bind operation at a time
bindExecutor = new PoliteExecutor("TcpPlugin", ioExecutor, 1);
} }
@Override @Override
@@ -113,38 +110,41 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
protected void bind() { protected void bind() {
bindExecutor.execute(() -> { ioExecutor.execute(new Runnable() {
if (!running) return; @Override
if (socket != null && !socket.isClosed()) return; public void run() {
ServerSocket ss = null; if (!running) return;
for (InetSocketAddress addr : getLocalSocketAddresses()) { ServerSocket ss = null;
try { for (InetSocketAddress addr : getLocalSocketAddresses()) {
ss = new ServerSocket(); try {
ss.bind(addr); ss = new ServerSocket();
break; ss.bind(addr);
} catch (IOException e) { break;
if (LOG.isLoggable(INFO)) } catch (IOException e) {
LOG.info("Failed to bind " + scrubSocketAddress(addr)); if (LOG.isLoggable(INFO))
tryToClose(ss); 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();
}); });
} }
@@ -218,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);
}
} }
}); });
} }
@@ -247,11 +251,10 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
continue; continue;
} }
Socket s = new Socket();
try { try {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.connect(remote); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -266,10 +269,6 @@ abstract class TcpPlugin implements DuplexPlugin {
return null; return null;
} }
protected Socket createSocket() throws IOException {
return new Socket();
}
@Nullable @Nullable
InetSocketAddress parseSocketAddress(String ipPort) { InetSocketAddress parseSocketAddress(String ipPort) {
if (StringUtils.isNullOrEmpty(ipPort)) return null; if (StringUtils.isNullOrEmpty(ipPort)) return null;
@@ -306,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();
} }
@@ -318,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,16 +64,18 @@ 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
public void startService() { public void startService() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
validateOutstandingMessagesAsync(); for (ClientId c : validators.keySet()) {
deliverOutstandingMessagesAsync(); validateOutstandingMessagesAsync(c);
shareOutstandingMessagesAsync(); deliverOutstandingMessagesAsync(c);
shareOutstandingMessagesAsync(c);
}
} }
@Override @Override
@@ -91,17 +93,22 @@ class ValidationManagerImpl implements ValidationManager, Service,
hooks.put(c, hook); hooks.put(c, hook);
} }
private void validateOutstandingMessagesAsync() { private void validateOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(this::validateOutstandingMessages); dbExecutor.execute(new Runnable() {
@Override
public void run() {
validateOutstandingMessages(c);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
private void validateOutstandingMessages() { 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)); unvalidated.addAll(db.getMessagesToValidate(txn, c));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -112,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
@@ -146,17 +158,22 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
} }
private void deliverOutstandingMessagesAsync() { private void deliverOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(this::deliverOutstandingMessages); dbExecutor.execute(new Runnable() {
@Override
public void run() {
deliverOutstandingMessages(c);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
private void deliverOutstandingMessages() { 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)); pending.addAll(db.getPendingMessages(txn, c));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -167,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
@@ -206,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);
@@ -231,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
@@ -248,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
@@ -297,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);
@@ -343,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());
@@ -351,17 +386,22 @@ class ValidationManagerImpl implements ValidationManager, Service,
return pending; return pending;
} }
private void shareOutstandingMessagesAsync() { private void shareOutstandingMessagesAsync(final ClientId c) {
dbExecutor.execute(this::shareOutstandingMessages); dbExecutor.execute(new Runnable() {
@Override
public void run() {
shareOutstandingMessages(c);
}
});
} }
@DatabaseExecutor @DatabaseExecutor
private void shareOutstandingMessages() { 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)); toShare.addAll(db.getMessagesToShare(txn, c));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -378,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
@@ -407,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
@@ -446,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());
@@ -464,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

@@ -4,9 +4,8 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.api.system.Scheduler;
import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -26,10 +25,7 @@ public class SystemModule {
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
public SystemModule() { public SystemModule() {
// Discard tasks that are submitted during shutdown scheduler = Executors.newSingleThreadScheduledExecutor();
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
scheduler = new ScheduledThreadPoolExecutor(1, policy);
} }
@Provides @Provides

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

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