mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
21 Commits
maintenanc
...
gradle-lib
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa79a31911 | ||
|
|
8171dd8bc9 | ||
|
|
4b88f0d9f1 | ||
|
|
116419f505 | ||
|
|
87b2624aa8 | ||
|
|
71fe6f3148 | ||
|
|
21df6cb809 | ||
|
|
1f0c385a5c | ||
|
|
986ea05fb2 | ||
|
|
030b52261d | ||
|
|
a50e13c2e3 | ||
|
|
c8326103b4 | ||
|
|
ddea031cbf | ||
|
|
f0d8532f71 | ||
|
|
4883d157dc | ||
|
|
a1bec1e927 | ||
|
|
37d4d79c64 | ||
|
|
05bc3f6a71 | ||
|
|
8b3960781a | ||
|
|
f3de4f53c5 | ||
|
|
166fc2948c |
@@ -12,8 +12,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 1621
|
||||
versionName "0.16.21"
|
||||
versionCode 1700
|
||||
versionName "0.17.0"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ dependencies {
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
}
|
||||
@@ -43,6 +43,7 @@ dependencyVerification {
|
||||
'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',
|
||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||
'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',
|
||||
@@ -54,16 +55,14 @@ dependencyVerification {
|
||||
}
|
||||
|
||||
ext.torBinaryDir = 'src/main/res/raw'
|
||||
ext.torVersion = '0.2.9.14'
|
||||
ext.geoipVersion = '2017-11-06'
|
||||
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
||||
|
||||
def torBinaries = [
|
||||
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
|
||||
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
|
||||
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
|
||||
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
|
||||
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||
"tor_arm" : '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688',
|
||||
"tor_arm_pie": '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283',
|
||||
"tor_x86" : '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab',
|
||||
"tor_x86_pie": '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288',
|
||||
"geoip" : 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
||||
]
|
||||
|
||||
def downloadBinary(name) {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
-dontwarn dagger.**
|
||||
-dontnote dagger.**
|
||||
|
||||
-keep class net.i2p.crypto.eddsa.** { *; }
|
||||
|
||||
-dontwarn sun.misc.Unsafe
|
||||
-dontnote com.google.common.**
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||
|
||||
@@ -23,7 +22,6 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
@@ -35,20 +33,18 @@ public class AndroidPluginModule {
|
||||
|
||||
@Provides
|
||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||
@Scheduler ScheduledExecutorService scheduler,
|
||||
AndroidExecutor androidExecutor, SecureRandom random,
|
||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||
Application app, LocationUtils locationUtils, DevReporter reporter,
|
||||
EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth =
|
||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||
appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||
appContext, locationUtils, reporter, eventBus,
|
||||
torSocketFactory, backoffFactory);
|
||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||
androidExecutor, appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
||||
locationUtils, reporter, eventBus, torSocketFactory,
|
||||
backoffFactory);
|
||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||
scheduler, backoffFactory, appContext);
|
||||
backoffFactory, appContext);
|
||||
Collection<DuplexPluginFactory> duplex =
|
||||
Arrays.asList(bluetooth, tor, lan);
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
||||
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
|
||||
private volatile boolean wasEnabledByUs = false;
|
||||
private volatile BluetoothStateReceiver receiver = null;
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
super.start();
|
||||
// Listen for changes to the Bluetooth state
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||
receiver = new BluetoothStateReceiver();
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialiseAdapter() throws IOException {
|
||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||
// with a message queue, so submit it to the AndroidExecutor
|
||||
try {
|
||||
adapter = androidExecutor.runOnBackgroundThread(
|
||||
BluetoothAdapter::getDefaultAdapter).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
if (adapter == null)
|
||||
throw new IOException("Bluetooth is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAdapterEnabled() {
|
||||
return adapter != null && adapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
void enableAdapter() {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void disableAdapterIfEnabledByUs() {
|
||||
if (isAdapterEnabled() && wasEnabledByUs) {
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
else LOG.info("Could not disable Bluetooth");
|
||||
wasEnabledByUs = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setEnabledByUs() {
|
||||
wasEnabledByUs = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
String getBluetoothAddress() {
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
||||
return address.isEmpty() ? null : address;
|
||||
}
|
||||
|
||||
@Override
|
||||
BluetoothServerSocket openServerSocket(String uuid) throws IOException {
|
||||
return adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", UUID.fromString(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
|
||||
throws IOException {
|
||||
return wrapSocket(ss.accept());
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||
return new AndroidBluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isValidAddress(String address) {
|
||||
return BluetoothAdapter.checkBluetoothAddress(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException {
|
||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||
UUID u = UUID.fromString(uuid);
|
||||
BluetoothSocket s = null;
|
||||
try {
|
||||
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
||||
s.connect();
|
||||
return wrapSocket(s);
|
||||
} catch (IOException e) {
|
||||
tryToClose(s);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Closeable c) {
|
||||
try {
|
||||
if (c != null) c.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) onAdapterEnabled();
|
||||
else if (state == STATE_OFF) onAdapterDisabled();
|
||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||
if (scanMode == SCAN_MODE_NONE) {
|
||||
LOG.info("Scan mode: None");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
||||
LOG.info("Scan mode: Connectable");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||
LOG.info("Scan mode: Discoverable");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
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 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.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.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 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(
|
||||
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, false)) {
|
||||
enableAdapter();
|
||||
} else {
|
||||
LOG.info("Not enabling Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
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() {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
tryToClose(socket);
|
||||
disableAdapter();
|
||||
}
|
||||
|
||||
private void disableAdapter() {
|
||||
if (adapter != null && adapter.isEnabled() && wasEnabledByUs) {
|
||||
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()) {
|
||||
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 (!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) {
|
||||
enableAdapterAsync();
|
||||
} else if (e instanceof DisableBluetoothEvent) {
|
||||
disableAdapterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void enableAdapterAsync() {
|
||||
ioExecutor.execute(this::enableAdapter);
|
||||
}
|
||||
|
||||
private void disableAdapterAsync() {
|
||||
ioExecutor.execute(this::disableAdapter);
|
||||
}
|
||||
|
||||
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 () -> {
|
||||
BluetoothSocket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new DroidtoothTransportConnection(
|
||||
DroidtoothPlugin.this, s), ID);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||
@@ -35,7 +35,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
private final EventBus eventBus;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
||||
public DroidtoothPluginFactory(Executor ioExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
@@ -61,7 +61,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
||||
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
|
||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
|
||||
@@ -11,12 +11,11 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
class AndroidBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
class DroidtoothTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final BluetoothSocket socket;
|
||||
|
||||
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
super(plugin);
|
||||
this.socket = socket;
|
||||
}
|
||||
@@ -5,84 +5,37 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
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.plugin.Backoff;
|
||||
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.ScheduledExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
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.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
|
||||
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 =
|
||||
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 ConnectivityManager connectivityManager;
|
||||
@Nullable
|
||||
private final WifiManager wifiManager;
|
||||
|
||||
@Nullable
|
||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||
private volatile SocketFactory socketFactory;
|
||||
|
||||
AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||
Backoff backoff, Context appContext, DuplexPluginCallback callback,
|
||||
int maxLatency, int maxIdleTime) {
|
||||
AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff,
|
||||
Context appContext, DuplexPluginCallback callback, int maxLatency,
|
||||
int maxIdleTime) {
|
||||
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
|
||||
this.scheduler = scheduler;
|
||||
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
|
||||
@@ -91,9 +44,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
running = true;
|
||||
// Register to receive network status events
|
||||
networkStateReceiver = new NetworkStateReceiver();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(CONNECTIVITY_ACTION);
|
||||
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
|
||||
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
||||
appContext.registerReceiver(networkStateReceiver, filter);
|
||||
}
|
||||
|
||||
@@ -105,92 +56,21 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
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 {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent i) {
|
||||
if (!running) return;
|
||||
if (isApEnabledEvent(i)) {
|
||||
// The state change may be broadcast before the AP address is
|
||||
// visible, so delay handling the event
|
||||
scheduler.schedule(this::handleConnectivityChange, 1, SECONDS);
|
||||
} else {
|
||||
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();
|
||||
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) {
|
||||
LOG.info("Connected to Wi-Fi");
|
||||
if (socket == null || socket.isClosed()) bind();
|
||||
} else if (addrs.isEmpty()) {
|
||||
LOG.info("Not connected to wifi");
|
||||
socketFactory = SocketFactory.getDefault();
|
||||
} else {
|
||||
LOG.info("Not connected to Wi-Fi");
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@@ -28,15 +27,12 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final BackoffFactory backoffFactory;
|
||||
private final Context appContext;
|
||||
|
||||
public AndroidLanTcpPluginFactory(Executor ioExecutor,
|
||||
ScheduledExecutorService scheduler, BackoffFactory backoffFactory,
|
||||
Context appContext) {
|
||||
BackoffFactory backoffFactory, Context appContext) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.backoffFactory = backoffFactory;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
@@ -55,7 +51,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
return new AndroidLanTcpPlugin(ioExecutor, scheduler, backoff,
|
||||
appContext, callback, MAX_LATENCY, MAX_IDLE_TIME);
|
||||
return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext,
|
||||
callback, MAX_LATENCY, MAX_IDLE_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import android.os.PowerManager;
|
||||
import net.freehaven.tor.control.EventHandler;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
@@ -60,10 +59,7 @@ import java.util.Map.Entry;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
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.MODE_PRIVATE;
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||
@@ -110,8 +101,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(TorPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor, connectionStatusExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Executor ioExecutor;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
private final DevReporter reporter;
|
||||
@@ -124,8 +114,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||
private final File doneFile, cookieFile;
|
||||
private final PowerManager.WakeLock wakeLock;
|
||||
private final AtomicReference<Future<?>> connectivityCheck =
|
||||
new AtomicReference<>();
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
@@ -134,13 +122,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private volatile TorControlConnection controlConnection = null;
|
||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||
|
||||
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||
Context appContext, LocationUtils locationUtils,
|
||||
DevReporter reporter, SocketFactory torSocketFactory,
|
||||
Backoff backoff, DuplexPluginCallback callback,
|
||||
String architecture, int maxLatency, int maxIdleTime) {
|
||||
TorPlugin(Executor ioExecutor, Context appContext,
|
||||
LocationUtils locationUtils, DevReporter reporter,
|
||||
SocketFactory torSocketFactory, Backoff backoff,
|
||||
DuplexPluginCallback callback, String architecture, int maxLatency,
|
||||
int maxIdleTime) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.appContext = appContext;
|
||||
this.locationUtils = locationUtils;
|
||||
this.reporter = reporter;
|
||||
@@ -165,9 +152,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
// This tag will prevent Huawei's powermanager from killing us.
|
||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||
wakeLock.setReferenceCounted(false);
|
||||
// Don't execute more than one connection status check at a time
|
||||
connectionStatusExecutor = new PoliteExecutor("TorPlugin",
|
||||
ioExecutor, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,11 +204,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
||||
if (stdout.hasNextLine()) {
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()){
|
||||
if(stdout.hasNextLine()) {
|
||||
LOG.info(stdout.nextLine());
|
||||
}
|
||||
if (stderr.hasNextLine()) {
|
||||
if(stderr.hasNextLine()){
|
||||
LOG.info(stderr.nextLine());
|
||||
}
|
||||
}
|
||||
@@ -273,11 +257,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
// Register to receive network status events
|
||||
networkStateReceiver = new NetworkStateReceiver();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(CONNECTIVITY_ACTION);
|
||||
filter.addAction(ACTION_SCREEN_ON);
|
||||
filter.addAction(ACTION_SCREEN_OFF);
|
||||
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
||||
appContext.registerReceiver(networkStateReceiver, filter);
|
||||
// Bind a server socket to receive incoming hidden service connections
|
||||
bind();
|
||||
@@ -614,7 +594,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -638,8 +618,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
@Override
|
||||
public void orConnStatus(String status, String orName) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||
if (status.equals("CLOSED") || status.equals("FAILED"))
|
||||
updateConnectionStatus(); // Check whether we've lost connectivity
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -679,7 +657,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event, @Nullable String path) {
|
||||
public void onEvent(int event, String path) {
|
||||
stopWatching();
|
||||
latch.countDown();
|
||||
}
|
||||
@@ -697,8 +675,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private void updateConnectionStatus() {
|
||||
connectionStatusExecutor.execute(() -> {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
|
||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
@@ -737,25 +716,14 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleConnectionStatusUpdate() {
|
||||
Future<?> newConnectivityCheck =
|
||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||
Future<?> oldConnectivityCheck =
|
||||
connectivityCheck.getAndSet(newConnectivityCheck);
|
||||
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
|
||||
}
|
||||
|
||||
private class NetworkStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent i) {
|
||||
if (!running) return;
|
||||
String action = i.getAction();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
|
||||
updateConnectionStatus();
|
||||
if (ACTION_SCREEN_ON.equals(action)
|
||||
|| ACTION_SCREEN_OFF.equals(action)) {
|
||||
scheduleConnectionStatusUpdate();
|
||||
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
||||
LOG.info("Detected connectivity change");
|
||||
updateConnectionStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -778,7 +746,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
private synchronized void enableNetwork(boolean enable) {
|
||||
networkEnabled = enable;
|
||||
if (!enable) circuitBuilt = false;
|
||||
circuitBuilt = false;
|
||||
}
|
||||
|
||||
private synchronized boolean isConnected() {
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
@@ -37,7 +36,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
private final DevReporter reporter;
|
||||
@@ -45,13 +43,11 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
private final SocketFactory torSocketFactory;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public TorPluginFactory(Executor ioExecutor,
|
||||
ScheduledExecutorService scheduler, Context appContext,
|
||||
public TorPluginFactory(Executor ioExecutor, Context appContext,
|
||||
LocationUtils locationUtils, DevReporter reporter,
|
||||
EventBus eventBus, SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.appContext = appContext;
|
||||
this.locationUtils = locationUtils;
|
||||
this.reporter = reporter;
|
||||
@@ -93,9 +89,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
||||
locationUtils, reporter, torSocketFactory, backoff, callback,
|
||||
architecture, MAX_LATENCY, MAX_IDLE_TIME);
|
||||
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
|
||||
reporter, torSocketFactory, backoff, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ targetCompatibility = 1.8
|
||||
apply plugin: 'witness'
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.dagger:dagger:2.0.2"
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation "org.jmock:jmock:2.8.2"
|
||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
||||
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
||||
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
testImplementation "org.jmock:jmock:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
|
||||
@@ -20,6 +20,10 @@ public interface CryptoComponent {
|
||||
|
||||
KeyParser getSignatureKeyParser();
|
||||
|
||||
KeyPair generateEdKeyPair();
|
||||
|
||||
KeyParser getEdKeyParser();
|
||||
|
||||
KeyParser getMessageKeyParser();
|
||||
|
||||
/**
|
||||
@@ -61,7 +65,6 @@ public interface CryptoComponent {
|
||||
* @param ourKeyPair our ephemeral keypair
|
||||
* @param alice true if ourKeyPair belongs to Alice
|
||||
* @return the shared secret
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||
boolean alice) throws GeneralSecurityException;
|
||||
@@ -106,7 +109,6 @@ public interface CryptoComponent {
|
||||
* @param ourKeyPair our ephemeral keypair
|
||||
* @param alice true if ourKeyPair belongs to Alice
|
||||
* @return the shared secret
|
||||
* @throws GeneralSecurityException
|
||||
*/
|
||||
SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||
boolean alice) throws GeneralSecurityException;
|
||||
@@ -130,7 +132,7 @@ public interface CryptoComponent {
|
||||
long streamNumber);
|
||||
|
||||
/**
|
||||
* Signs the given byte[] with the given PrivateKey.
|
||||
* Signs the given byte[] with the given ECDSA private key.
|
||||
*
|
||||
* @param label A label specific to this signature
|
||||
* to ensure that the signature cannot be repurposed
|
||||
@@ -139,8 +141,17 @@ public interface CryptoComponent {
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Verifies that the given signature is valid for the signedData
|
||||
* and the given publicKey.
|
||||
* Signs the given byte[] with the given Ed25519 private key.
|
||||
*
|
||||
* @param label A label specific to this signature
|
||||
* to ensure that the signature cannot be repurposed
|
||||
*/
|
||||
byte[] signEd(String label, byte[] toSign, byte[] privateKey)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Verifies that the given signature is valid for the signed data
|
||||
* and the given ECDSA public key.
|
||||
*
|
||||
* @param label A label that was specific to this signature
|
||||
* to ensure that the signature cannot be repurposed
|
||||
@@ -149,6 +160,17 @@ public interface CryptoComponent {
|
||||
boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Verifies that the given signature is valid for the signed data
|
||||
* and the given Ed25519 public key.
|
||||
*
|
||||
* @param label A label that was specific to this signature
|
||||
* to ensure that the signature cannot be repurposed
|
||||
* @return true if the signature was valid, false otherwise.
|
||||
*/
|
||||
boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Returns the hash of the given inputs. The inputs are unambiguously
|
||||
* combined by prefixing each input with its length.
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when the database uses a newer schema than the current code.
|
||||
*/
|
||||
public class DataTooNewException extends DbException {
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
/**
|
||||
* Thrown when the database uses an older schema than the current code and
|
||||
* cannot be migrated.
|
||||
*/
|
||||
public class DataTooOldException extends DbException {
|
||||
}
|
||||
@@ -37,13 +37,8 @@ public interface DatabaseComponent {
|
||||
|
||||
/**
|
||||
* Opens the database and returns true if the database already existed.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||
boolean open() throws DbException;
|
||||
|
||||
/**
|
||||
* Waits for any open transactions to finish and closes the database.
|
||||
@@ -259,30 +254,31 @@ public interface DatabaseComponent {
|
||||
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/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToValidate(Transaction txn)
|
||||
Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that are pending delivery due to
|
||||
* dependencies on other messages.
|
||||
* Returns the IDs of any messages that are valid but pending delivery due
|
||||
* to dependencies on other messages for the given client.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getPendingMessages(Transaction txn)
|
||||
Collection<MessageId> getPendingMessages(Transaction txn, ClientId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that have shared dependents but have
|
||||
* not yet been shared themselves.
|
||||
* Returns the IDs of any messages from the given client
|
||||
* that have a shared dependent, but are still not shared themselves.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToShare(Transaction txn)
|
||||
throws DbException;
|
||||
Collection<MessageId> getMessagesToShare(Transaction txn,
|
||||
ClientId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the message with the given ID, in serialised form, or null if
|
||||
@@ -377,16 +373,6 @@ public interface DatabaseComponent {
|
||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
/*
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
||||
* no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all settings in the given namespace.
|
||||
* <p/>
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* An class for managing a particular key agreement listener.
|
||||
@@ -24,11 +24,11 @@ public abstract class KeyAgreementListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until an incoming connection is received and returns it.
|
||||
*
|
||||
* @throws IOException if an error occurs or {@link #close()} is called.
|
||||
* Starts listening for incoming connections, and returns a Callable that
|
||||
* will return a KeyAgreementConnection when an incoming connection is
|
||||
* received.
|
||||
*/
|
||||
public abstract KeyAgreementConnection accept() throws IOException;
|
||||
public abstract Callable<KeyAgreementConnection> listen();
|
||||
|
||||
/**
|
||||
* Closes the underlying server socket.
|
||||
|
||||
@@ -21,25 +21,7 @@ public interface LifecycleManager {
|
||||
* The result of calling {@link #startServices(String)}.
|
||||
*/
|
||||
enum StartResult {
|
||||
ALREADY_RUNNING,
|
||||
DB_ERROR,
|
||||
DATA_TOO_OLD_ERROR,
|
||||
DATA_TOO_NEW_ERROR,
|
||||
SERVICE_ERROR,
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
/**
|
||||
* The state the lifecycle can be in.
|
||||
* Returned by {@link #getLifecycleState()}
|
||||
*/
|
||||
enum LifecycleState {
|
||||
|
||||
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
|
||||
|
||||
public boolean isAfter(LifecycleState state) {
|
||||
return ordinal() > state.ordinal();
|
||||
}
|
||||
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,10 +71,4 @@ public interface LifecycleManager {
|
||||
* the {@link DatabaseComponent} to be closed before returning.
|
||||
*/
|
||||
void waitForShutdown() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Returns the current state of the lifecycle.
|
||||
*/
|
||||
LifecycleState getLifecycleState();
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.briarproject.bramble.api.lifecycle.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the app enters a new lifecycle state.
|
||||
*/
|
||||
public class LifecycleEvent extends Event {
|
||||
|
||||
private final LifecycleState state;
|
||||
|
||||
public LifecycleEvent(LifecycleState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.briarproject.bramble.api.lifecycle.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the app is shutting down.
|
||||
*/
|
||||
public class ShutdownEvent extends Event {
|
||||
}
|
||||
@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Attempts to connect to the remote peer specified in the given descriptor.
|
||||
* Returns null if no connection can be established.
|
||||
* Returns null if no connection can be established within the given time.
|
||||
*/
|
||||
@Nullable
|
||||
DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] remoteCommitment, BdfList descriptor);
|
||||
byte[] remoteCommitment, BdfList descriptor, long timeout);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that informs the Bluetooth plugin that we have enabled the
|
||||
* Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class BluetoothEnabledEvent extends Event {
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -36,4 +36,8 @@ public class ClientId implements Comparable<ClientId> {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,10 +126,6 @@ public class StringUtils {
|
||||
return toUtf8(s).length > maxLength;
|
||||
}
|
||||
|
||||
public static boolean isValidMac(String mac) {
|
||||
return MAC.matcher(mac).matches();
|
||||
}
|
||||
|
||||
public static byte[] macToBytes(String mac) {
|
||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||
return fromHexString(mac.replaceAll(":", ""));
|
||||
|
||||
@@ -2,27 +2,12 @@ package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.util.IoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
private static final AtomicInteger nextTestDir =
|
||||
@@ -53,50 +38,4 @@ public class TestUtils {
|
||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||
}
|
||||
|
||||
public static LocalAuthor getLocalAuthor() {
|
||||
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||
}
|
||||
|
||||
public static LocalAuthor getLocalAuthor(int nameLength) {
|
||||
AuthorId id = new AuthorId(getRandomId());
|
||||
String name = getRandomString(nameLength);
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
long created = System.currentTimeMillis();
|
||||
return new LocalAuthor(id, name, publicKey, privateKey, created);
|
||||
}
|
||||
|
||||
public static Author getAuthor() {
|
||||
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||
}
|
||||
|
||||
public static Author getAuthor(int nameLength) {
|
||||
AuthorId id = new AuthorId(getRandomId());
|
||||
String name = getRandomString(nameLength);
|
||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
return new Author(id, name, publicKey);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId) {
|
||||
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||
return getGroup(clientId, descriptorLength);
|
||||
}
|
||||
|
||||
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
||||
GroupId groupId = new GroupId(getRandomId());
|
||||
byte[] descriptor = getRandomBytes(descriptorLength);
|
||||
return new Group(groupId, clientId, descriptor);
|
||||
}
|
||||
|
||||
public static Message getMessage(GroupId groupId) {
|
||||
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
|
||||
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
|
||||
}
|
||||
|
||||
public static Message getMessage(GroupId groupId, int rawLength) {
|
||||
MessageId id = new MessageId(getRandomId());
|
||||
byte[] raw = getRandomBytes(rawLength);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return new Message(id, groupId, timestamp, raw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,19 @@ dependencies {
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||
|
||||
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
apt "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
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"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
testImplementation "org.jmock:jmock:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
|
||||
testApt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
testApt "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
@@ -37,6 +38,7 @@ dependencyVerification {
|
||||
'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',
|
||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||
'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',
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
@@ -56,6 +60,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
|
||||
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
|
||||
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
||||
private static final int ED_KEY_PAIR_BITS = 256;
|
||||
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
||||
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
||||
private static final int PBKDF_TARGET_MILLIS = 500;
|
||||
@@ -99,6 +104,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
private final ECKeyPairGenerator signatureKeyPairGenerator;
|
||||
private final KeyParser agreementKeyParser, signatureKeyParser;
|
||||
private final MessageEncrypter messageEncrypter;
|
||||
private final KeyPairGenerator edKeyPairGenerator;
|
||||
private final KeyParser edKeyParser;
|
||||
|
||||
@Inject
|
||||
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
|
||||
@@ -132,6 +139,9 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
|
||||
SIGNATURE_KEY_PAIR_BITS);
|
||||
messageEncrypter = new MessageEncrypter(secureRandom);
|
||||
edKeyPairGenerator = new KeyPairGenerator();
|
||||
edKeyPairGenerator.initialize(ED_KEY_PAIR_BITS, secureRandom);
|
||||
edKeyParser = new EdKeyParser();
|
||||
}
|
||||
|
||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||
@@ -190,6 +200,21 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return secret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateEdKeyPair() {
|
||||
java.security.KeyPair keyPair = edKeyPairGenerator.generateKeyPair();
|
||||
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
|
||||
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
|
||||
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
|
||||
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyParser getEdKeyParser() {
|
||||
return edKeyParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateAgreementKeyPair() {
|
||||
AsymmetricCipherKeyPair keyPair =
|
||||
@@ -416,19 +441,41 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
@Override
|
||||
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||
throws GeneralSecurityException {
|
||||
Signature signature = new SignatureImpl(secureRandom);
|
||||
KeyParser keyParser = getSignatureKeyParser();
|
||||
return sign(new SignatureImpl(secureRandom), signatureKeyParser, label,
|
||||
toSign, privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] signEd(String label, byte[] toSign, byte[] privateKey)
|
||||
throws GeneralSecurityException {
|
||||
return sign(new EdSignature(), edKeyParser, label, toSign, privateKey);
|
||||
}
|
||||
|
||||
private byte[] sign(Signature sig, KeyParser keyParser, String label,
|
||||
byte[] toSign, byte[] privateKey) throws GeneralSecurityException {
|
||||
PrivateKey key = keyParser.parsePrivateKey(privateKey);
|
||||
signature.initSign(key);
|
||||
updateSignature(signature, label, toSign);
|
||||
return signature.sign();
|
||||
sig.initSign(key);
|
||||
updateSignature(sig, label, toSign);
|
||||
return sig.sign();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException {
|
||||
Signature sig = new SignatureImpl(secureRandom);
|
||||
KeyParser keyParser = getSignatureKeyParser();
|
||||
return verify(new SignatureImpl(secureRandom), signatureKeyParser,
|
||||
label, signedData, publicKey, signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException {
|
||||
return verify(new EdSignature(), edKeyParser, label, signedData,
|
||||
publicKey, signature);
|
||||
}
|
||||
|
||||
private boolean verify(Signature sig, KeyParser keyParser, String label,
|
||||
byte[] signedData, byte[] publicKey, byte[] signature)
|
||||
throws GeneralSecurityException {
|
||||
PublicKey key = keyParser.parsePublicKey(publicKey);
|
||||
sig.initVerify(key);
|
||||
updateSignature(sig, label, signedData);
|
||||
@@ -436,7 +483,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
private void updateSignature(Signature signature, String label,
|
||||
byte[] toSign) {
|
||||
byte[] toSign) throws GeneralSecurityException {
|
||||
byte[] labelBytes = StringUtils.toUtf8(label);
|
||||
byte[] length = new byte[INT_32_BYTES];
|
||||
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
@NotNullByDefault
|
||||
class EdKeyParser implements KeyParser {
|
||||
|
||||
@Override
|
||||
public PublicKey parsePublicKey(byte[] encodedKey)
|
||||
throws GeneralSecurityException {
|
||||
if (encodedKey.length != 32) throw new GeneralSecurityException();
|
||||
return new EdPublicKey(encodedKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey parsePrivateKey(byte[] encodedKey)
|
||||
throws GeneralSecurityException {
|
||||
if (encodedKey.length != 32) throw new GeneralSecurityException();
|
||||
return new EdPrivateKey(encodedKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
class EdPrivateKey extends Bytes implements PrivateKey {
|
||||
|
||||
EdPrivateKey(byte[] bytes) {
|
||||
super(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
class EdPublicKey extends Bytes implements PublicKey {
|
||||
|
||||
EdPublicKey(byte[] bytes) {
|
||||
super(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return getBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
|
||||
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
|
||||
|
||||
@NotNullByDefault
|
||||
class EdSignature implements Signature {
|
||||
|
||||
private static final Provider PROVIDER = new EdDSASecurityProvider();
|
||||
|
||||
private static final EdDSANamedCurveSpec CURVE_SPEC =
|
||||
EdDSANamedCurveTable.getByName("Ed25519");
|
||||
|
||||
private final java.security.Signature signature;
|
||||
|
||||
EdSignature() {
|
||||
try {
|
||||
signature = java.security.Signature
|
||||
.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSign(PrivateKey k) throws GeneralSecurityException {
|
||||
if (!(k instanceof EdPrivateKey))
|
||||
throw new IllegalArgumentException();
|
||||
EdDSAPrivateKey privateKey = new EdDSAPrivateKey(
|
||||
new EdDSAPrivateKeySpec(k.getEncoded(), CURVE_SPEC));
|
||||
signature.initSign(privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initVerify(PublicKey k) throws GeneralSecurityException {
|
||||
if (!(k instanceof EdPublicKey))
|
||||
throw new IllegalArgumentException();
|
||||
EdDSAPublicKey publicKey = new EdDSAPublicKey(
|
||||
new EdDSAPublicKeySpec(k.getEncoded(), CURVE_SPEC));
|
||||
signature.initVerify(publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte b) throws GeneralSecurityException {
|
||||
signature.update(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] b) throws GeneralSecurityException {
|
||||
signature.update(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] b, int off, int len)
|
||||
throws GeneralSecurityException {
|
||||
signature.update(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign() throws GeneralSecurityException {
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] sig) throws GeneralSecurityException {
|
||||
return signature.verify(sig);
|
||||
}
|
||||
}
|
||||
@@ -22,25 +22,25 @@ interface Signature {
|
||||
/**
|
||||
* @see {@link java.security.Signature#update(byte)}
|
||||
*/
|
||||
void update(byte b);
|
||||
void update(byte b) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* @see {@link java.security.Signature#update(byte[])}
|
||||
*/
|
||||
void update(byte[] b);
|
||||
void update(byte[] b) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* @see {@link java.security.Signature#update(byte[], int, int)}
|
||||
*/
|
||||
void update(byte[] b, int off, int len);
|
||||
void update(byte[] b, int off, int len) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* @see {@link java.security.Signature#sign()}
|
||||
*/
|
||||
byte[] sign();
|
||||
byte[] sign() throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* @see {@link java.security.Signature#verify(byte[])}
|
||||
*/
|
||||
boolean verify(byte[] signature);
|
||||
boolean verify(byte[] signature) throws GeneralSecurityException;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,8 @@ package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -40,13 +37,8 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Opens the database and returns true if the database already existed.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||
boolean open() throws DbException;
|
||||
|
||||
/**
|
||||
* Prevents new transactions from starting, waits for all current
|
||||
@@ -97,12 +89,9 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@Nullable ContactId sender) throws DbException;
|
||||
void addMessage(T txn, Message m, State state, boolean shared)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -273,7 +272,7 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
|
||||
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -424,37 +423,31 @@ interface Database<T> {
|
||||
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/>
|
||||
* 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
|
||||
* dependencies on other messages.
|
||||
* Returns the IDs of any messages that are still pending due to
|
||||
* dependencies to other messages for the given client.
|
||||
* <p/>
|
||||
* 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
|
||||
* not yet been shared themselves.
|
||||
* Returns the IDs of any messages from the given client
|
||||
* that have a shared dependent, but are still not shared themselves.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToShare(T txn) 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;
|
||||
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* contact.
|
||||
@@ -580,6 +580,12 @@ interface Database<T> {
|
||||
void removeOfferedMessages(T txn, ContactId c,
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||
@@ -91,6 +90,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
private final ReentrantReadWriteLock lock =
|
||||
new ReentrantReadWriteLock(true);
|
||||
|
||||
private volatile int shutdownHandle = -1;
|
||||
|
||||
@Inject
|
||||
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
||||
ShutdownManager shutdown) {
|
||||
@@ -101,22 +102,23 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(@Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
boolean reopened = db.open(listener);
|
||||
shutdown.addShutdownHook(() -> {
|
||||
public boolean open() throws DbException {
|
||||
Runnable shutdownHook = () -> {
|
||||
try {
|
||||
close();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
});
|
||||
};
|
||||
boolean reopened = db.open();
|
||||
shutdownHandle = shutdown.addShutdownHook(shutdownHook);
|
||||
return reopened;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws DbException {
|
||||
if (closed.getAndSet(true)) return;
|
||||
shutdown.removeShutdownHook(shutdownHandle);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@@ -215,7 +217,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsGroup(txn, m.getGroupId()))
|
||||
throw new NoSuchGroupException();
|
||||
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 MessageStateChangedEvent(m.getId(), true,
|
||||
DELIVERED));
|
||||
@@ -224,6 +226,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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
|
||||
public void addTransport(Transaction transaction, TransportId t,
|
||||
int maxLatency) throws DbException {
|
||||
@@ -455,24 +467,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||
throws DbException {
|
||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction,
|
||||
ClientId c) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getMessagesToValidate(txn);
|
||||
return db.getMessagesToValidate(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getPendingMessages(Transaction transaction)
|
||||
throws DbException {
|
||||
public Collection<MessageId> getPendingMessages(Transaction transaction,
|
||||
ClientId c) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getPendingMessages(txn);
|
||||
return db.getPendingMessages(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToShare(Transaction transaction)
|
||||
throws DbException {
|
||||
public Collection<MessageId> getMessagesToShare(
|
||||
Transaction transaction, ClientId c) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getMessagesToShare(txn);
|
||||
return db.getMessagesToShare(txn, c);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -571,13 +583,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessageDependents(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings getSettings(Transaction transaction, String namespace)
|
||||
throws DbException {
|
||||
@@ -672,7 +677,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.raiseSeenFlag(txn, c, m.getId());
|
||||
db.raiseAckFlag(txn, c, m.getId());
|
||||
} else {
|
||||
db.addMessage(txn, m, UNKNOWN, false, c);
|
||||
addMessage(txn, m, UNKNOWN, false, c);
|
||||
transaction.attach(new MessageAddedEvent(m, c));
|
||||
}
|
||||
transaction.attach(new MessageToAckEvent(c));
|
||||
@@ -740,8 +745,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
GroupId id = g.getId();
|
||||
if (!db.containsGroup(txn, id))
|
||||
throw new NoSuchGroupException();
|
||||
Collection<ContactId> affected =
|
||||
db.getGroupVisibility(txn, id).keySet();
|
||||
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
|
||||
db.removeGroup(txn, id);
|
||||
transaction.attach(new GroupRemovedEvent(g));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
@@ -811,9 +815,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchGroupException();
|
||||
Visibility old = db.getGroupVisibility(txn, c, g);
|
||||
if (old == v) return;
|
||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||
if (old == INVISIBLE) {
|
||||
db.addGroupVisibility(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);
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@@ -23,4 +23,10 @@ interface DatabaseConstants {
|
||||
*/
|
||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||
|
||||
/**
|
||||
* The {@link Settings} key under which the minimum supported database
|
||||
* schema version is stored.
|
||||
*/
|
||||
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
@@ -14,7 +13,6 @@ import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
@@ -42,11 +40,10 @@ class H2Database extends JdbcDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(@Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
public boolean open() throws DbException {
|
||||
boolean reopen = config.databaseExists();
|
||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
||||
super.open("org.h2.Driver", reopen, listener);
|
||||
super.open("org.h2.Driver", reopen);
|
||||
return reopen;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,9 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbClosedException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -34,7 +31,6 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -51,7 +47,6 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
@@ -62,6 +57,7 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MIN_SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
|
||||
@@ -72,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
@NotNullByDefault
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 32;
|
||||
private static final int SCHEMA_VERSION = 30;
|
||||
private static final int MIN_SCHEMA_VERSION = 30;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -152,16 +148,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
private static final String CREATE_MESSAGE_METADATA =
|
||||
"CREATE TABLE messageMetadata"
|
||||
+ " (messageId HASH NOT NULL,"
|
||||
+ " groupId HASH NOT NULL," // Denormalised
|
||||
+ " state INT NOT NULL," // Denormalised
|
||||
+ " key VARCHAR NOT NULL,"
|
||||
+ " value BINARY NOT NULL,"
|
||||
+ " PRIMARY KEY (messageId, key),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
||||
@@ -189,13 +180,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE TABLE statuses"
|
||||
+ " (messageId HASH NOT NULL,"
|
||||
+ " contactId INT NOT NULL,"
|
||||
+ " groupId HASH NOT NULL," // Denormalised
|
||||
+ " timestamp BIGINT NOT NULL," // Denormalised
|
||||
+ " length INT NOT NULL," // Denormalised
|
||||
+ " state INT NOT NULL," // Denormalised
|
||||
+ " groupShared BOOLEAN NOT NULL," // Denormalised
|
||||
+ " messageShared BOOLEAN NOT NULL," // Denormalised
|
||||
+ " deleted BOOLEAN NOT NULL," // Denormalised
|
||||
+ " ack BOOLEAN NOT NULL,"
|
||||
+ " seen BOOLEAN NOT NULL,"
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
@@ -207,9 +191,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_TRANSPORTS =
|
||||
@@ -255,21 +236,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
||||
+ " ON contacts (authorId)";
|
||||
|
||||
private static final String INDEX_MESSAGES_BY_GROUP_ID =
|
||||
"CREATE INDEX IF NOT EXISTS messagesByGroupId"
|
||||
+ " ON messages (groupId)";
|
||||
|
||||
private static final String INDEX_OFFERS_BY_CONTACT_ID =
|
||||
"CREATE INDEX IF NOT EXISTS offersByContactId"
|
||||
+ " ON offers (contactId)";
|
||||
|
||||
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
||||
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
||||
+ " ON groups (clientId)";
|
||||
|
||||
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||
+ " ON messageMetadata (groupId, state)";
|
||||
private static final String INDEX_MESSAGE_METADATA_BY_MESSAGE_ID =
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByMessageId"
|
||||
+ " ON messageMetadata (messageId)";
|
||||
|
||||
private static final String INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID =
|
||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdGroupId"
|
||||
+ " ON statuses (contactId, groupId)";
|
||||
|
||||
private static final String INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP =
|
||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
||||
+ " ON statuses (contactId, timestamp)";
|
||||
private static final String INDEX_GROUP_METADATA_BY_GROUP_ID =
|
||||
"CREATE INDEX IF NOT EXISTS groupMetadataByGroupId"
|
||||
+ " ON groupMetadata (groupId)";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JdbcDatabase.class.getName());
|
||||
@@ -299,8 +284,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
protected void open(String driverClass, boolean reopen,
|
||||
@Nullable MigrationListener listener) throws DbException {
|
||||
protected void open(String driverClass, boolean reopen) throws DbException {
|
||||
// Load the JDBC driver
|
||||
try {
|
||||
Class.forName(driverClass);
|
||||
@@ -311,10 +295,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
Connection txn = startTransaction();
|
||||
try {
|
||||
if (reopen) {
|
||||
checkSchemaVersion(txn, listener);
|
||||
if (!checkSchemaVersion(txn)) throw new DbException();
|
||||
} else {
|
||||
createTables(txn);
|
||||
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
|
||||
storeSchemaVersion(txn);
|
||||
}
|
||||
createIndexes(txn);
|
||||
commitTransaction(txn);
|
||||
@@ -324,51 +308,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the schema version stored in the database with the schema
|
||||
* version used by the current code and applies any suitable migrations to
|
||||
* the data if necessary.
|
||||
*
|
||||
* @throws DataTooNewException if the data uses a newer schema than the
|
||||
* current code
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
private void checkSchemaVersion(Connection txn,
|
||||
@Nullable MigrationListener listener) throws DbException {
|
||||
private boolean checkSchemaVersion(Connection txn) throws DbException {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
if (dataSchemaVersion == -1) throw new DbException();
|
||||
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return;
|
||||
if (CODE_SCHEMA_VERSION < dataSchemaVersion)
|
||||
throw new DataTooNewException();
|
||||
// Apply any suitable migrations in order
|
||||
for (Migration<Connection> m : getMigrations()) {
|
||||
int start = m.getStartVersion(), end = m.getEndVersion();
|
||||
if (start == dataSchemaVersion) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Migrating from schema " + start + " to " + end);
|
||||
if (listener != null) listener.onMigrationRun();
|
||||
// Apply the migration
|
||||
m.migrate(txn);
|
||||
// Store the new schema version
|
||||
storeSchemaVersion(txn, end);
|
||||
dataSchemaVersion = end;
|
||||
}
|
||||
}
|
||||
if (dataSchemaVersion != CODE_SCHEMA_VERSION)
|
||||
throw new DataTooOldException();
|
||||
int schemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
if (schemaVersion == SCHEMA_VERSION) return true;
|
||||
if (schemaVersion < MIN_SCHEMA_VERSION) return false;
|
||||
int minSchemaVersion = s.getInt(MIN_SCHEMA_VERSION_KEY, -1);
|
||||
return SCHEMA_VERSION >= minSchemaVersion;
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
List<Migration<Connection>> getMigrations() {
|
||||
return Arrays.asList(new Migration30_31(), new Migration31_32());
|
||||
}
|
||||
|
||||
private void storeSchemaVersion(Connection txn, int version)
|
||||
throws DbException {
|
||||
private void storeSchemaVersion(Connection txn) throws DbException {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||
s.putInt(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
|
||||
s.putInt(MIN_SCHEMA_VERSION_KEY, MIN_SCHEMA_VERSION);
|
||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
@@ -418,10 +370,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||
s.executeUpdate(INDEX_MESSAGES_BY_GROUP_ID);
|
||||
s.executeUpdate(INDEX_OFFERS_BY_CONTACT_ID);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_MESSAGE_ID);
|
||||
s.executeUpdate(INDEX_GROUP_METADATA_BY_GROUP_ID);
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
@@ -547,8 +500,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
// Create a contact row
|
||||
String sql = "INSERT INTO contacts"
|
||||
+ " (authorId, name, publicKey, localAuthorId,"
|
||||
+ " verified, active)"
|
||||
+ " (authorId, name, publicKey, localAuthorId, verified, active)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, remote.getId().getBytes());
|
||||
@@ -599,7 +551,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
@Override
|
||||
public void addGroupVisibility(Connection txn, ContactId c, GroupId g,
|
||||
boolean groupShared) throws DbException {
|
||||
boolean shared) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groupVisibilities"
|
||||
@@ -608,50 +560,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setBoolean(3, groupShared);
|
||||
ps.setBoolean(3, shared);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Create a status row for each message in the group
|
||||
addStatus(txn, c, g, groupShared);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addStatus(Connection txn, ContactId c, GroupId g,
|
||||
boolean groupShared) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId, timestamp, state, shared,"
|
||||
+ " length, raw IS NULL"
|
||||
+ " FROM messages"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
MessageId id = new MessageId(rs.getBytes(1));
|
||||
long timestamp = rs.getLong(2);
|
||||
State state = State.fromValue(rs.getInt(3));
|
||||
boolean messageShared = rs.getBoolean(4);
|
||||
int length = rs.getInt(5);
|
||||
boolean deleted = rs.getBoolean(6);
|
||||
boolean seen = removeOfferedMessage(txn, c, id);
|
||||
addStatus(txn, id, c, g, timestamp, length, state, groupShared,
|
||||
messageShared, deleted, seen);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalAuthor(Connection txn, LocalAuthor a)
|
||||
throws DbException {
|
||||
@@ -677,8 +595,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
@Override
|
||||
public void addMessage(Connection txn, Message m, State state,
|
||||
boolean messageShared, @Nullable ContactId sender)
|
||||
throws DbException {
|
||||
boolean shared) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||
@@ -689,24 +606,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setBytes(2, m.getGroupId().getBytes());
|
||||
ps.setLong(3, m.getTimestamp());
|
||||
ps.setInt(4, state.getValue());
|
||||
ps.setBoolean(5, messageShared);
|
||||
ps.setBoolean(5, shared);
|
||||
byte[] raw = m.getRaw();
|
||||
ps.setInt(6, raw.length);
|
||||
ps.setBytes(7, raw);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Create a status row for each contact that can see the group
|
||||
Map<ContactId, Boolean> visibility =
|
||||
getGroupVisibility(txn, m.getGroupId());
|
||||
for (Entry<ContactId, Boolean> e : visibility.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
boolean offered = removeOfferedMessage(txn, c, m.getId());
|
||||
boolean seen = offered || (sender != null && c.equals(sender));
|
||||
addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(),
|
||||
m.getLength(), state, e.getValue(), messageShared,
|
||||
false, seen);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -744,28 +650,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private void addStatus(Connection txn, MessageId m, ContactId c, GroupId g,
|
||||
long timestamp, int length, State state, boolean groupShared,
|
||||
boolean messageShared, boolean deleted, boolean seen)
|
||||
throws DbException {
|
||||
@Override
|
||||
public void addStatus(Connection txn, ContactId c, MessageId m, boolean ack,
|
||||
boolean seen) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
||||
+ " timestamp, length, state, groupShared, messageShared,"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0)";
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, ack,"
|
||||
+ " seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, ?, ?, FALSE, 0, 0)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setBytes(3, g.getBytes());
|
||||
ps.setLong(4, timestamp);
|
||||
ps.setInt(5, length);
|
||||
ps.setInt(6, state.getValue());
|
||||
ps.setBoolean(7, groupShared);
|
||||
ps.setBoolean(8, messageShared);
|
||||
ps.setBoolean(9, deleted);
|
||||
ps.setBoolean(10, seen);
|
||||
ps.setBoolean(11, seen);
|
||||
ps.setBoolean(3, ack);
|
||||
ps.setBoolean(4, seen);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -1017,9 +914,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?"
|
||||
+ " AND messageShared = TRUE";
|
||||
String sql = "SELECT NULL FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " WHERE messageId = ?"
|
||||
+ " AND contactId = ?"
|
||||
+ " AND m.shared = TRUE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1071,13 +971,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (affected < 0) throw new DbStateException();
|
||||
if (affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET deleted = TRUE WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -1300,19 +1193,18 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ContactId, Boolean> getGroupVisibility(Connection txn, GroupId g)
|
||||
public Collection<ContactId> getGroupVisibility(Connection txn, GroupId g)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT contactId, shared FROM groupVisibilities"
|
||||
String sql = "SELECT contactId FROM groupVisibilities"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Map<ContactId, Boolean> visible = new HashMap<>();
|
||||
while (rs.next())
|
||||
visible.put(new ContactId(rs.getInt(1)), rs.getBoolean(2));
|
||||
List<ContactId> visible = new ArrayList<>();
|
||||
while (rs.next()) visible.add(new ContactId(rs.getInt(1)));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return visible;
|
||||
@@ -1438,13 +1330,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
try {
|
||||
// Retrieve the message IDs for each query term and intersect
|
||||
Set<MessageId> intersection = null;
|
||||
String sql = "SELECT messageId FROM messageMetadata"
|
||||
+ " WHERE groupId = ? AND state = ?"
|
||||
String sql = "SELECT m.messageId"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN messageMetadata AS md"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE state = ? AND groupId = ?"
|
||||
+ " AND key = ? AND value = ?";
|
||||
for (Entry<String, byte[]> e : query.entrySet()) {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setString(3, e.getKey());
|
||||
ps.setBytes(4, e.getValue());
|
||||
rs = ps.executeQuery();
|
||||
@@ -1472,20 +1367,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId, key, value"
|
||||
+ " FROM messageMetadata"
|
||||
+ " WHERE groupId = ? AND state = ?";
|
||||
String sql = "SELECT m.messageId, key, value"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN messageMetadata AS md"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE state = ? AND groupId = ?"
|
||||
+ " ORDER BY m.messageId";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Map<MessageId, Metadata> all = new HashMap<>();
|
||||
Metadata metadata = null;
|
||||
MessageId lastMessageId = null;
|
||||
while (rs.next()) {
|
||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||
Metadata metadata = all.get(messageId);
|
||||
if (metadata == null) {
|
||||
if (lastMessageId == null || !messageId.equals(lastMessageId)) {
|
||||
metadata = new Metadata();
|
||||
all.put(messageId, metadata);
|
||||
lastMessageId = messageId;
|
||||
}
|
||||
metadata.put(rs.getString(2), rs.getBytes(3));
|
||||
}
|
||||
@@ -1540,8 +1440,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT key, value FROM messageMetadata"
|
||||
+ " WHERE state = ? AND messageId = ?";
|
||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE m.state = ? AND md.messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
@@ -1564,9 +1466,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT key, value FROM messageMetadata"
|
||||
+ " WHERE (state = ? OR state = ?)"
|
||||
+ " AND messageId = ?";
|
||||
String sql = "SELECT key, value FROM messageMetadata AS md"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON m.messageId = md.messageId"
|
||||
+ " WHERE (m.state = ? OR m.state = ?)"
|
||||
+ " AND md.messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setInt(2, PENDING.getValue());
|
||||
@@ -1590,8 +1494,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId, txCount > 0, seen FROM statuses"
|
||||
+ " WHERE groupId = ? AND contactId = ?";
|
||||
String sql = "SELECT m.messageId, txCount > 0, seen"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " WHERE groupId = ?"
|
||||
+ " AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1614,13 +1522,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageStatus getMessageStatus(Connection txn, ContactId c,
|
||||
MessageId m) throws DbException {
|
||||
public MessageStatus getMessageStatus(Connection txn,
|
||||
ContactId c, MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT txCount > 0, seen FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
String sql = "SELECT txCount > 0, seen"
|
||||
+ " FROM statuses"
|
||||
+ " WHERE messageId = ?"
|
||||
+ " AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1762,10 +1672,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
String sql = "SELECT m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
+ " AND seen = FALSE AND requested = FALSE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp LIMIT ?";
|
||||
@@ -1819,10 +1733,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
+ " AND seen = FALSE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp";
|
||||
@@ -1850,26 +1768,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToValidate(Connection txn)
|
||||
throws DbException {
|
||||
return getMessagesInState(txn, UNKNOWN);
|
||||
public Collection<MessageId> getMessagesToValidate(Connection txn,
|
||||
ClientId c) throws DbException {
|
||||
return getMessagesInState(txn, c, UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getPendingMessages(Connection txn)
|
||||
throws DbException {
|
||||
return getMessagesInState(txn, PENDING);
|
||||
public Collection<MessageId> getPendingMessages(Connection txn,
|
||||
ClientId c) throws DbException {
|
||||
return getMessagesInState(txn, c, PENDING);
|
||||
}
|
||||
|
||||
private Collection<MessageId> getMessagesInState(Connection txn,
|
||||
private Collection<MessageId> getMessagesInState(Connection txn, ClientId c,
|
||||
State state) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId FROM messages"
|
||||
+ " WHERE state = ? AND raw IS NOT NULL";
|
||||
String sql = "SELECT messageId FROM messages AS m"
|
||||
+ " JOIN groups AS g ON m.groupId = g.groupId"
|
||||
+ " WHERE state = ? AND clientId = ? AND raw IS NOT NULL";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setString(2, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
@@ -1884,8 +1804,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToShare(Connection txn)
|
||||
throws DbException {
|
||||
public Collection<MessageId> getMessagesToShare(
|
||||
Connection txn, ClientId c) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -1894,10 +1814,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " ON m.messageId = d.dependencyId"
|
||||
+ " JOIN messages AS m1"
|
||||
+ " ON d.messageId = m1.messageId"
|
||||
+ " WHERE m.state = ?"
|
||||
+ " AND m.shared = FALSE AND m1.shared = TRUE";
|
||||
+ " JOIN groups AS g"
|
||||
+ " ON m.groupId = g.groupId"
|
||||
+ " WHERE m.shared = FALSE AND m1.shared = TRUE"
|
||||
+ " AND g.clientId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
ps.setString(1, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
@@ -1911,36 +1833,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT expiry FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " ORDER BY expiry LIMIT 1";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
long nextSendTime = Long.MAX_VALUE;
|
||||
if (rs.next()) {
|
||||
nextSendTime = rs.getLong(1);
|
||||
if (rs.next()) throw new AssertionError();
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return nextSendTime;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] getRawMessage(Connection txn, MessageId m)
|
||||
@@ -1972,10 +1864,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
+ " AND seen = FALSE AND requested = TRUE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp";
|
||||
@@ -2148,7 +2044,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != requested.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected) {
|
||||
for (int rows: batchAffected) {
|
||||
if (rows < 0) throw new DbStateException();
|
||||
if (rows > 1) throw new DbStateException();
|
||||
}
|
||||
@@ -2162,92 +2058,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
@Override
|
||||
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||
g.getBytes(), meta, "groupMetadata", "groupId");
|
||||
if (added.isEmpty()) return;
|
||||
// Insert any keys that don't already exist
|
||||
String sql = "INSERT INTO groupMetadata (groupId, key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setBytes(3, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != added.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
mergeMetadata(txn, g.getBytes(), meta, "groupMetadata", "groupId");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeMessageMetadata(Connection txn, MessageId m,
|
||||
Metadata meta) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||
m.getBytes(), meta, "messageMetadata", "messageId");
|
||||
if (added.isEmpty()) return;
|
||||
// Get the group ID and message state for the denormalised columns
|
||||
String sql = "SELECT groupId, state FROM messages"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId g = new GroupId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Insert any keys that don't already exist
|
||||
sql = "INSERT INTO messageMetadata"
|
||||
+ " (messageId, groupId, state, key, value)"
|
||||
+ " VALUES (?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setInt(3, state.getValue());
|
||||
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||
ps.setString(4, e.getKey());
|
||||
ps.setBytes(5, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != added.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
public void mergeMessageMetadata(Connection txn, MessageId m, Metadata meta)
|
||||
throws DbException {
|
||||
mergeMetadata(txn, m.getBytes(), meta, "messageMetadata", "messageId");
|
||||
}
|
||||
|
||||
// Removes or updates any existing entries, returns any entries that
|
||||
// need to be added
|
||||
private Map<String, byte[]> removeOrUpdateMetadata(Connection txn,
|
||||
byte[] id, Metadata meta, String tableName, String columnName)
|
||||
throws DbException {
|
||||
private void mergeMetadata(Connection txn, byte[] id, Metadata meta,
|
||||
String tableName, String columnName) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Determine which keys are being removed
|
||||
List<String> removed = new ArrayList<>();
|
||||
Map<String, byte[]> notRemoved = new HashMap<>();
|
||||
Map<String, byte[]> retained = new HashMap<>();
|
||||
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||
if (e.getValue() == REMOVE) removed.add(e.getKey());
|
||||
else notRemoved.put(e.getKey(), e.getValue());
|
||||
else retained.put(e.getKey(), e.getValue());
|
||||
}
|
||||
// Delete any keys that are being removed
|
||||
if (!removed.isEmpty()) {
|
||||
@@ -2268,33 +2097,45 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
ps.close();
|
||||
}
|
||||
if (notRemoved.isEmpty()) return Collections.emptyMap();
|
||||
if (retained.isEmpty()) return;
|
||||
// Update any keys that already exist
|
||||
String sql = "UPDATE " + tableName + " SET value = ?"
|
||||
+ " WHERE " + columnName + " = ? AND key = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(2, id);
|
||||
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
||||
ps.setBytes(1, e.getValue());
|
||||
ps.setString(3, e.getKey());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != notRemoved.size())
|
||||
if (batchAffected.length != retained.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected) {
|
||||
if (rows < 0) throw new DbStateException();
|
||||
if (rows > 1) throw new DbStateException();
|
||||
}
|
||||
ps.close();
|
||||
// Are there any keys that don't already exist?
|
||||
Map<String, byte[]> added = new HashMap<>();
|
||||
int updateIndex = 0;
|
||||
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||
if (batchAffected[updateIndex++] == 0)
|
||||
added.put(e.getKey(), e.getValue());
|
||||
// Insert any keys that don't already exist
|
||||
sql = "INSERT INTO " + tableName
|
||||
+ " (" + columnName + ", key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, id);
|
||||
int updateIndex = 0, inserted = 0;
|
||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
||||
if (batchAffected[updateIndex] == 0) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setBytes(3, e.getValue());
|
||||
ps.addBatch();
|
||||
inserted++;
|
||||
}
|
||||
updateIndex++;
|
||||
}
|
||||
return added;
|
||||
batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != inserted) throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2451,8 +2292,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Remove status rows for the messages in the group
|
||||
for (MessageId m : getMessageIds(txn, g)) removeStatus(txn, c, m);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2492,7 +2331,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeOfferedMessage(Connection txn, ContactId c,
|
||||
@Override
|
||||
public boolean removeOfferedMessage(Connection txn, ContactId c,
|
||||
MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
@@ -2536,15 +2376,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
private void removeStatus(Connection txn, ContactId c, MessageId m)
|
||||
@Override
|
||||
public void removeStatus(Connection txn, ContactId c, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "DELETE FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
+ " WHERE contactId = ? AND messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -2640,16 +2481,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET groupShared = ?"
|
||||
+ " WHERE contactId = ? AND groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBoolean(1, shared);
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setBytes(3, g.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2657,8 +2488,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessageShared(Connection txn, MessageId m)
|
||||
throws DbException {
|
||||
public void setMessageShared(Connection txn, MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE messages SET shared = TRUE"
|
||||
@@ -2668,14 +2498,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET messageShared = TRUE"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2694,22 +2516,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in messageMetadata
|
||||
sql = "UPDATE messageMetadata SET state = ? WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET state = ? WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
interface Migration<T> {
|
||||
|
||||
/**
|
||||
* Returns the schema version from which this migration starts.
|
||||
*/
|
||||
int getStartVersion();
|
||||
|
||||
/**
|
||||
* Returns the schema version at which this migration ends.
|
||||
*/
|
||||
int getEndVersion();
|
||||
|
||||
void migrate(T txn) throws DbException;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
class Migration30_31 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Migration30_31.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 31;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
// Add groupId column
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD COLUMN groupId BINARY(32) AFTER messageId");
|
||||
// Populate groupId column
|
||||
s.execute("UPDATE messageMetadata AS mm SET groupId ="
|
||||
+ " (SELECT groupId FROM messages AS m"
|
||||
+ " WHERE mm.messageId = m.messageId)");
|
||||
// Add not null constraint now column has been populated
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ALTER COLUMN groupId"
|
||||
+ " SET NOT NULL");
|
||||
// Add foreign key constraint
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD CONSTRAINT groupIdForeignKey"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE");
|
||||
// Add state column
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ADD COLUMN state INT AFTER groupId");
|
||||
// Populate state column
|
||||
s.execute("UPDATE messageMetadata AS mm SET state ="
|
||||
+ " (SELECT state FROM messages AS m"
|
||||
+ " WHERE mm.messageId = m.messageId)");
|
||||
// Add not null constraint now column has been populated
|
||||
s.execute("ALTER TABLE messageMetadata"
|
||||
+ " ALTER COLUMN state"
|
||||
+ " SET NOT NULL");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Statement s) {
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
} catch (SQLException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
interface ConnectionChooser {
|
||||
|
||||
/**
|
||||
* Submits a connection task to the chooser.
|
||||
*/
|
||||
void submit(Callable<KeyAgreementConnection> task);
|
||||
|
||||
/**
|
||||
* Returns a connection returned by any of the tasks submitted to the
|
||||
* chooser, waiting up to the given amount of time for a connection if
|
||||
* necessary. Returns null if the time elapses without a connection
|
||||
* becoming available.
|
||||
*
|
||||
* @param timeout the timeout in milliseconds
|
||||
* @throws InterruptedException if the thread is interrupted while waiting
|
||||
* for a connection to become available
|
||||
*/
|
||||
@Nullable
|
||||
KeyAgreementConnection poll(long timeout) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Stops the chooser. Any connections already returned to the chooser are
|
||||
* closed unless they have been removed from the chooser by calling
|
||||
* {@link #poll(long)}. Any connections subsequently returned to the
|
||||
* chooser will also be closed.
|
||||
*/
|
||||
void stop();
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@NotNullByDefault
|
||||
@ThreadSafe
|
||||
class ConnectionChooserImpl implements ConnectionChooser {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectionChooserImpl.class.getName());
|
||||
|
||||
private final Clock clock;
|
||||
private final Executor ioExecutor;
|
||||
private final Object lock = new Object();
|
||||
|
||||
// The following are locking: lock
|
||||
private boolean stopped = false;
|
||||
private final Queue<KeyAgreementConnection> results = new LinkedList<>();
|
||||
|
||||
@Inject
|
||||
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
|
||||
this.clock = clock;
|
||||
this.ioExecutor = ioExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(Callable<KeyAgreementConnection> task) {
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
KeyAgreementConnection c = task.call();
|
||||
if (c != null) addResult(c);
|
||||
} catch (Exception e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection poll(long timeout)
|
||||
throws InterruptedException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + timeout;
|
||||
synchronized (lock) {
|
||||
while (!stopped && results.isEmpty() && now < end) {
|
||||
lock.wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
return results.poll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
List<KeyAgreementConnection> unused;
|
||||
synchronized (lock) {
|
||||
unused = new ArrayList<>(results);
|
||||
results.clear();
|
||||
stopped = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing " + unused.size() + " unused connections");
|
||||
for (KeyAgreementConnection c : unused) tryToClose(c.getConnection());
|
||||
}
|
||||
|
||||
private void addResult(KeyAgreementConnection c) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Got connection for " + c.getTransportId());
|
||||
boolean close = false;
|
||||
synchronized (lock) {
|
||||
if (stopped) {
|
||||
close = true;
|
||||
} else {
|
||||
results.add(c);
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
if (close) {
|
||||
LOG.info("Already stopped");
|
||||
tryToClose(c.getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
try {
|
||||
conn.getReader().dispose(false, true);
|
||||
conn.getWriter().dispose(false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,19 +13,23 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
||||
@@ -41,27 +45,29 @@ class KeyAgreementConnector {
|
||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||
|
||||
private final Callbacks callbacks;
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final PluginManager pluginManager;
|
||||
private final ConnectionChooser connectionChooser;
|
||||
private final CompletionService<KeyAgreementConnection> connect;
|
||||
|
||||
private final List<KeyAgreementListener> listeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final CountDownLatch aliceLatch = new CountDownLatch(1);
|
||||
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
|
||||
private final List<KeyAgreementListener> listeners = new ArrayList<>();
|
||||
private final List<Future<KeyAgreementConnection>> pending =
|
||||
new ArrayList<>();
|
||||
|
||||
private volatile boolean alice = false, stopped = false;
|
||||
private volatile boolean connecting = false;
|
||||
private volatile boolean alice = false;
|
||||
|
||||
KeyAgreementConnector(Callbacks callbacks,
|
||||
KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
||||
CryptoComponent crypto, PluginManager pluginManager,
|
||||
ConnectionChooser connectionChooser) {
|
||||
Executor ioExecutor) {
|
||||
this.callbacks = callbacks;
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionChooser = connectionChooser;
|
||||
connect = new ExecutorCompletionService<>(ioExecutor);
|
||||
}
|
||||
|
||||
Payload listen(KeyPair localKeyPair) {
|
||||
public Payload listen(KeyPair localKeyPair) {
|
||||
LOG.info("Starting BQP listeners");
|
||||
// Derive commitment
|
||||
byte[] commitment = crypto.deriveKeyCommitment(
|
||||
@@ -74,9 +80,8 @@ class KeyAgreementConnector {
|
||||
if (l != null) {
|
||||
TransportId id = plugin.getId();
|
||||
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
|
||||
pending.add(connect.submit(new ReadableTask(l.listen())));
|
||||
listeners.add(l);
|
||||
connectionChooser.submit(new ReadableTask(l::accept));
|
||||
}
|
||||
}
|
||||
return new Payload(commitment, descriptors);
|
||||
@@ -84,92 +89,125 @@ class KeyAgreementConnector {
|
||||
|
||||
void stopListening() {
|
||||
LOG.info("Stopping BQP listeners");
|
||||
stopped = true;
|
||||
aliceLatch.countDown();
|
||||
for (KeyAgreementListener l : listeners) l.close();
|
||||
connectionChooser.stop();
|
||||
for (KeyAgreementListener l : listeners) {
|
||||
l.close();
|
||||
}
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
|
||||
// Let the ReadableTasks know if we are Alice
|
||||
public KeyAgreementTransport connect(Payload remotePayload,
|
||||
boolean alice) {
|
||||
// Let the listeners know if we are Alice
|
||||
this.connecting = true;
|
||||
this.alice = alice;
|
||||
aliceLatch.countDown();
|
||||
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||
|
||||
// Start connecting over supported transports
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Starting outgoing BQP connections as "
|
||||
+ (alice ? "Alice" : "Bob"));
|
||||
}
|
||||
LOG.info("Starting outgoing BQP connections");
|
||||
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
||||
Plugin p = pluginManager.getPlugin(d.getId());
|
||||
if (p instanceof DuplexPlugin) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting via " + d.getId());
|
||||
DuplexPlugin plugin = (DuplexPlugin) p;
|
||||
byte[] commitment = remotePayload.getCommitment();
|
||||
BdfList descriptor = d.getDescriptor();
|
||||
connectionChooser.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, commitment, descriptor)));
|
||||
pending.add(connect.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, remotePayload.getCommitment(),
|
||||
d.getDescriptor(), end))));
|
||||
}
|
||||
}
|
||||
|
||||
// Get chosen connection
|
||||
KeyAgreementConnection chosen = null;
|
||||
try {
|
||||
KeyAgreementConnection chosen =
|
||||
connectionChooser.poll(CONNECTION_TIMEOUT);
|
||||
if (chosen == null) return null;
|
||||
long now = clock.currentTimeMillis();
|
||||
Future<KeyAgreementConnection> f =
|
||||
connect.poll(end - now, MILLISECONDS);
|
||||
if (f == null)
|
||||
return null; // No task completed within the timeout.
|
||||
chosen = f.get();
|
||||
return new KeyAgreementTransport(chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
} catch (ExecutionException | IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
stopListening();
|
||||
// Close all other connections
|
||||
closePending(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitingForAlice() {
|
||||
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
|
||||
private void closePending(@Nullable KeyAgreementConnection chosen) {
|
||||
for (Future<KeyAgreementConnection> f : pending) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else if (!f.isCancelled()) {
|
||||
KeyAgreementConnection c = f.get();
|
||||
if (c != null && c != chosen)
|
||||
tryToClose(c.getConnection(), false);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing connection, exception: " + exception);
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final byte[] commitment;
|
||||
private final BdfList descriptor;
|
||||
private final long end;
|
||||
private final DuplexPlugin plugin;
|
||||
|
||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
||||
BdfList descriptor) {
|
||||
BdfList descriptor, long end) {
|
||||
this.plugin = plugin;
|
||||
this.commitment = commitment;
|
||||
this.descriptor = descriptor;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
// Repeat attempts until we connect, get stopped, or get interrupted
|
||||
while (!stopped) {
|
||||
// Repeat attempts until we connect, get interrupted, or time out
|
||||
while (true) {
|
||||
long now = clock.currentTimeMillis();
|
||||
if (now > end) throw new IOException();
|
||||
DuplexTransportConnection conn =
|
||||
plugin.createKeyAgreementConnection(commitment,
|
||||
descriptor);
|
||||
descriptor, end - now);
|
||||
if (conn != null) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(plugin.getId() + ": Outgoing connection");
|
||||
LOG.info(plugin.getId().getString() +
|
||||
": Outgoing connection");
|
||||
return new KeyAgreementConnection(conn, plugin.getId());
|
||||
}
|
||||
// Wait 2s before retry (to circumvent transient failures)
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReadableTask implements Callable<KeyAgreementConnection> {
|
||||
private class ReadableTask
|
||||
implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final Callable<KeyAgreementConnection> connectionTask;
|
||||
|
||||
@@ -177,23 +215,24 @@ class KeyAgreementConnector {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
KeyAgreementConnection c = connectionTask.call();
|
||||
if (c == null) return null;
|
||||
aliceLatch.await();
|
||||
if (alice || stopped) return c;
|
||||
// Bob waits here for Alice to scan his QR code, determine her
|
||||
// role, and send her key
|
||||
InputStream in = c.getConnection().getReader().getInputStream();
|
||||
while (!stopped && in.available() == 0) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId() + ": Waiting for data");
|
||||
waitingForAlice();
|
||||
Thread.sleep(500);
|
||||
boolean waitingSent = false;
|
||||
while (!alice && in.available() == 0) {
|
||||
if (!waitingSent && connecting && !alice) {
|
||||
// Bob waits here until Alice obtains his payload.
|
||||
callbacks.connectionWaiting();
|
||||
waitingSent = true;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(c.getTransportId().getString() +
|
||||
": Waiting for connection");
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (!stopped && LOG.isLoggable(INFO))
|
||||
if (!alice && LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId().getString() + ": Data available");
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -13,9 +22,13 @@ import dagger.Provides;
|
||||
public class KeyAgreementModule {
|
||||
|
||||
@Provides
|
||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(
|
||||
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) {
|
||||
return keyAgreementTaskFactory;
|
||||
@Singleton
|
||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
|
||||
CryptoComponent crypto, EventBus eventBus,
|
||||
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager) {
|
||||
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
|
||||
ioExecutor, payloadEncoder, pluginManager);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -27,10 +40,4 @@ public class KeyAgreementModule {
|
||||
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
||||
return new PayloadParserImpl(bdfReaderFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ConnectionChooser provideConnectionChooser(
|
||||
ConnectionChooserImpl connectionChooser) {
|
||||
return connectionChooser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +89,7 @@ class KeyAgreementProtocol {
|
||||
byte[] theirPublicKey;
|
||||
if (alice) {
|
||||
sendKey();
|
||||
// Alice waits here for Bob to scan her QR code, determine his
|
||||
// role, receive her key and respond with his key
|
||||
// Alice waits here until Bob obtains her payload.
|
||||
callbacks.connectionWaiting();
|
||||
theirPublicKey = receiveKey();
|
||||
} else {
|
||||
|
||||
@@ -5,37 +5,42 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
||||
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final EventBus eventBus;
|
||||
private final Executor ioExecutor;
|
||||
private final PayloadEncoder payloadEncoder;
|
||||
private final PluginManager pluginManager;
|
||||
private final Provider<ConnectionChooser> connectionChooserProvider;
|
||||
|
||||
@Inject
|
||||
KeyAgreementTaskFactoryImpl(CryptoComponent crypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
Provider<ConnectionChooser> connectionChooserProvider) {
|
||||
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, @IoExecutor Executor ioExecutor,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionChooserProvider = connectionChooserProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementTask createTask() {
|
||||
return new KeyAgreementTaskImpl(crypto, eventBus, payloadEncoder,
|
||||
pluginManager, connectionChooserProvider.get());
|
||||
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
|
||||
pluginManager, ioExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,16 +17,19 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
|
||||
class KeyAgreementTaskImpl extends Thread implements
|
||||
KeyAgreementTask, KeyAgreementConnector.Callbacks,
|
||||
KeyAgreementProtocol.Callbacks {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||
@@ -40,15 +43,15 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
private Payload localPayload;
|
||||
private Payload remotePayload;
|
||||
|
||||
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
ConnectionChooser connectionChooser) {
|
||||
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager, Executor ioExecutor) {
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
localKeyPair = crypto.generateAgreementKeyPair();
|
||||
connector = new KeyAgreementConnector(this, crypto, pluginManager,
|
||||
connectionChooser);
|
||||
connector = new KeyAgreementConnector(this, clock, crypto,
|
||||
pluginManager, ioExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,8 +65,10 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
@Override
|
||||
public synchronized void stopListening() {
|
||||
if (localPayload != null) {
|
||||
if (remotePayload == null) connector.stopListening();
|
||||
else interrupt();
|
||||
if (remotePayload == null)
|
||||
connector.stopListening();
|
||||
else
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,8 @@ package org.briarproject.bramble.lifecycle;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
@@ -15,7 +12,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
|
||||
@@ -32,21 +29,14 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
class LifecycleManagerImpl implements LifecycleManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LifecycleManagerImpl.class.getName());
|
||||
@@ -64,8 +54,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
|
||||
private volatile LifecycleState state = STARTING;
|
||||
|
||||
@Inject
|
||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||
CryptoComponent crypto, AuthorFactory authorFactory,
|
||||
@@ -131,7 +119,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
LOG.info("Starting services");
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
boolean reopened = db.open(this);
|
||||
boolean reopened = db.open();
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
if (reopened)
|
||||
@@ -143,10 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
registerLocalAuthor(createLocalAuthor(nickname));
|
||||
}
|
||||
|
||||
state = STARTING_SERVICES;
|
||||
dbLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
for (Client c : clients) {
|
||||
@@ -172,17 +157,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
+ " took " + duration + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
state = RUNNING;
|
||||
startupLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||
return SUCCESS;
|
||||
} catch (DataTooOldException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DATA_TOO_OLD_ERROR;
|
||||
} catch (DataTooNewException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DATA_TOO_NEW_ERROR;
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DB_ERROR;
|
||||
@@ -194,12 +170,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMigrationRun() {
|
||||
state = MIGRATING_DATABASE;
|
||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServices() {
|
||||
try {
|
||||
@@ -210,8 +180,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
}
|
||||
try {
|
||||
LOG.info("Stopping services");
|
||||
state = STOPPING;
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
eventBus.broadcast(new ShutdownEvent());
|
||||
for (Service s : services) {
|
||||
long start = System.currentTimeMillis();
|
||||
s.stopService();
|
||||
@@ -256,8 +225,4 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
shutdownLatch.await();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -26,7 +25,6 @@ import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -52,7 +50,7 @@ class Poller implements EventListener {
|
||||
private final SecureRandom random;
|
||||
private final Clock clock;
|
||||
private final Lock lock;
|
||||
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
|
||||
private final Map<TransportId, PollTask> tasks; // Locking: lock
|
||||
|
||||
@Inject
|
||||
Poller(@IoExecutor Executor ioExecutor,
|
||||
@@ -95,10 +93,6 @@ class Poller implements EventListener {
|
||||
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
||||
// Poll the newly enabled transport
|
||||
pollNow(t.getTransportId());
|
||||
} else if (e instanceof TransportDisabledEvent) {
|
||||
TransportDisabledEvent t = (TransportDisabledEvent) e;
|
||||
// Cancel polling for the disabled transport
|
||||
cancel(t.getTransportId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,31 +151,18 @@ class Poller implements EventListener {
|
||||
TransportId t = p.getId();
|
||||
lock.lock();
|
||||
try {
|
||||
ScheduledPollTask scheduled = tasks.get(t);
|
||||
if (scheduled == null || due < scheduled.task.due) {
|
||||
// If a later task exists, cancel it. If it's already started
|
||||
// it will abort safely when it finds it's been replaced
|
||||
if (scheduled != null) scheduled.future.cancel(false);
|
||||
PollTask scheduled = tasks.get(t);
|
||||
if (scheduled == null || due < scheduled.due) {
|
||||
PollTask task = new PollTask(p, due, randomiseNext);
|
||||
Future future = scheduler.schedule(
|
||||
tasks.put(t, task);
|
||||
scheduler.schedule(
|
||||
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
||||
tasks.put(t, new ScheduledPollTask(task, future));
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancel(TransportId t) {
|
||||
lock.lock();
|
||||
try {
|
||||
ScheduledPollTask scheduled = tasks.remove(t);
|
||||
if (scheduled != null) scheduled.future.cancel(false);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private void poll(Plugin p) {
|
||||
TransportId t = p.getId();
|
||||
@@ -189,17 +170,6 @@ class Poller implements EventListener {
|
||||
p.poll(connectionRegistry.getConnectedContacts(t));
|
||||
}
|
||||
|
||||
private class ScheduledPollTask {
|
||||
|
||||
private final PollTask task;
|
||||
private final Future future;
|
||||
|
||||
private ScheduledPollTask(PollTask task, Future future) {
|
||||
this.task = task;
|
||||
this.future = future;
|
||||
}
|
||||
}
|
||||
|
||||
private class PollTask implements Runnable {
|
||||
|
||||
private final Plugin plugin;
|
||||
@@ -218,9 +188,7 @@ class Poller implements EventListener {
|
||||
lock.lock();
|
||||
try {
|
||||
TransportId t = plugin.getId();
|
||||
ScheduledPollTask scheduled = tasks.get(t);
|
||||
if (scheduled != null && scheduled.task != this)
|
||||
return; // Replaced by another task
|
||||
if (tasks.get(t) != this) return; // Replaced by another task
|
||||
tasks.remove(t);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
||||
@@ -23,8 +23,9 @@ import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -43,9 +44,6 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LanTcpPlugin.class.getName());
|
||||
|
||||
private static final LanAddressComparator ADDRESS_COMPARATOR =
|
||||
new LanAddressComparator();
|
||||
|
||||
private static final int MAX_ADDRESSES = 4;
|
||||
private static final String SEPARATOR = ",";
|
||||
|
||||
@@ -65,18 +63,19 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
String oldIpPorts = p.get(PROP_IP_PORTS);
|
||||
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
||||
List<InetSocketAddress> locals = new ArrayList<>();
|
||||
List<InetSocketAddress> locals = new LinkedList<>();
|
||||
for (InetAddress local : getLocalIpAddresses()) {
|
||||
if (isAcceptableAddress(local)) {
|
||||
// If this is the old address, try to use the same port
|
||||
for (InetSocketAddress old : olds) {
|
||||
if (old.getAddress().equals(local))
|
||||
locals.add(new InetSocketAddress(local, old.getPort()));
|
||||
if (old.getAddress().equals(local)) {
|
||||
int port = old.getPort();
|
||||
locals.add(0, new InetSocketAddress(local, port));
|
||||
}
|
||||
}
|
||||
locals.add(new InetSocketAddress(local, 0));
|
||||
}
|
||||
}
|
||||
Collections.sort(locals, ADDRESS_COMPARATOR);
|
||||
return locals;
|
||||
}
|
||||
|
||||
@@ -154,39 +153,17 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
// Package access for testing
|
||||
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
||||
// 10.0.0.0/8
|
||||
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
|
||||
if (localIp[0] == 10) return remoteIp[0] == 10;
|
||||
// 172.16.0.0/12
|
||||
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
|
||||
if (localIp[0] == (byte) 172 && (localIp[1] & 0xF0) == 16)
|
||||
return remoteIp[0] == (byte) 172 && (remoteIp[1] & 0xF0) == 16;
|
||||
// 192.168.0.0/16
|
||||
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
|
||||
if (localIp[0] == (byte) 192 && localIp[1] == (byte) 168)
|
||||
return remoteIp[0] == (byte) 192 && remoteIp[1] == (byte) 168;
|
||||
// Unrecognised prefix - may be compatible
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isPrefix10(byte[] ipv4) {
|
||||
return ipv4[0] == 10;
|
||||
}
|
||||
|
||||
private static boolean isPrefix172(byte[] ipv4) {
|
||||
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
|
||||
}
|
||||
|
||||
private static boolean isPrefix192(byte[] ipv4) {
|
||||
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
|
||||
}
|
||||
|
||||
// Returns the prefix length for an RFC 1918 address, or 0 for any other
|
||||
// address
|
||||
private static int getRfc1918PrefixLength(InetAddress addr) {
|
||||
if (!(addr instanceof Inet4Address)) return 0;
|
||||
if (!addr.isSiteLocalAddress()) return 0;
|
||||
byte[] ipv4 = addr.getAddress();
|
||||
if (isPrefix10(ipv4)) return 8;
|
||||
if (isPrefix172(ipv4)) return 12;
|
||||
if (isPrefix192(ipv4)) return 16;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
@@ -223,7 +200,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
if (!isRunning()) return null;
|
||||
InetSocketAddress remote;
|
||||
try {
|
||||
@@ -241,11 +218,10 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Socket s = new Socket();
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||
Socket s = createSocket();
|
||||
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
||||
s.connect(remote);
|
||||
s.setSoTimeout(socketTimeout);
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -283,11 +259,14 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
Socket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
return new KeyAgreementConnection(new TcpTransportConnection(
|
||||
LanTcpPlugin.this, s), ID);
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return () -> {
|
||||
Socket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new TcpTransportConnection(LanTcpPlugin.this, s), ID);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -299,19 +278,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
@@ -48,7 +47,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(TcpPlugin.class.getName());
|
||||
|
||||
protected final Executor ioExecutor, bindExecutor;
|
||||
protected final Executor ioExecutor;
|
||||
protected final Backoff backoff;
|
||||
protected final DuplexPluginCallback callback;
|
||||
protected final int maxLatency, maxIdleTime, socketTimeout;
|
||||
@@ -91,8 +90,6 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
||||
socketTimeout = Integer.MAX_VALUE;
|
||||
else socketTimeout = maxIdleTime * 2;
|
||||
// Don't execute more than one bind operation at a time
|
||||
bindExecutor = new PoliteExecutor("TcpPlugin", ioExecutor, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -113,9 +110,8 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
protected void bind() {
|
||||
bindExecutor.execute(() -> {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
if (socket != null && !socket.isClosed()) return;
|
||||
ServerSocket ss = null;
|
||||
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
||||
try {
|
||||
@@ -247,11 +243,10 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Socket s = new Socket();
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||
Socket s = createSocket();
|
||||
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
||||
s.connect(remote);
|
||||
s.setSoTimeout(socketTimeout);
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -266,10 +261,6 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Socket createSocket() throws IOException {
|
||||
return new Socket();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
InetSocketAddress parseSocketAddress(String ipPort) {
|
||||
if (StringUtils.isNullOrEmpty(ipPort)) return null;
|
||||
@@ -306,7 +297,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
@Override
|
||||
public void createLocalState(Transaction txn) throws DbException {
|
||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||
db.addGroup(txn, localGroup);
|
||||
// Ensure we've set things up for any pre-existing contacts
|
||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||
@@ -130,8 +129,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public Map<TransportId, TransportProperties> getLocalProperties()
|
||||
throws DbException {
|
||||
Map<TransportId, TransportProperties> local;
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
local = getLocalProperties(txn);
|
||||
db.commitTransaction(txn);
|
||||
@@ -166,8 +164,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
throws DbException {
|
||||
try {
|
||||
TransportProperties p = null;
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
// Find the latest local update
|
||||
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
|
||||
@@ -193,8 +190,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public Map<ContactId, TransportProperties> getRemoteProperties(
|
||||
TransportId t) throws DbException {
|
||||
Map<ContactId, TransportProperties> remote = new HashMap<>();
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
for (Contact c : db.getContacts(txn))
|
||||
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
||||
@@ -228,8 +224,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
TransportProperties p;
|
||||
// TODO: Transaction can be read-only when code is simplified
|
||||
Transaction txn = db.startTransaction(false);
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
p = getRemoteProperties(txn, db.getContact(txn, c), t);
|
||||
db.commitTransaction(txn);
|
||||
@@ -319,7 +314,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
|
||||
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
|
||||
throws DbException, FormatException {
|
||||
// TODO: This can be simplified before 1.0
|
||||
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
|
||||
Map<MessageId, BdfDictionary> metadata = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId());
|
||||
@@ -327,17 +321,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
BdfDictionary meta = e.getValue();
|
||||
TransportId t = new TransportId(meta.getString("transportId"));
|
||||
long version = meta.getLong("version");
|
||||
LatestUpdate latest = latestUpdates.get(t);
|
||||
if (latest == null) {
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
} else if (version > latest.version) {
|
||||
// This update is newer - delete the previous one
|
||||
db.removeMessage(txn, latest.messageId);
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
} else {
|
||||
// We've already found a newer update - delete this one
|
||||
db.removeMessage(txn, e.getKey());
|
||||
}
|
||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||
}
|
||||
return latestUpdates;
|
||||
}
|
||||
@@ -345,38 +329,16 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
@Nullable
|
||||
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
||||
boolean local) throws DbException, FormatException {
|
||||
// TODO: This can be simplified before 1.0
|
||||
LatestUpdate latest = null;
|
||||
Map<MessageId, BdfDictionary> metadata =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||
BdfDictionary meta = e.getValue();
|
||||
if (meta.getString("transportId").equals(t.getString())
|
||||
&& meta.getBoolean("local") == local) {
|
||||
long version = meta.getLong("version");
|
||||
if (latest == null) {
|
||||
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 new LatestUpdate(e.getKey(), meta.getLong("version"));
|
||||
}
|
||||
}
|
||||
return latest;
|
||||
return null;
|
||||
}
|
||||
|
||||
private TransportProperties parseProperties(BdfList message)
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
@@ -29,8 +29,6 @@ import java.util.Collection;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
@@ -38,7 +36,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
@@ -52,14 +49,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD
|
||||
@NotNullByDefault
|
||||
class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
// Check for retransmittable records once every 60 seconds
|
||||
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
||||
|
||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
||||
};
|
||||
private static final ThrowingRunnable<IOException>
|
||||
NEXT_SEND_TIME_DECREASED = () -> {
|
||||
};
|
||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {};
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor dbExecutor;
|
||||
@@ -70,13 +65,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final RecordWriter recordWriter;
|
||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||
|
||||
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
||||
private final AtomicBoolean generateBatchQueued = new AtomicBoolean(false);
|
||||
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
|
||||
private final AtomicBoolean generateRequestQueued =
|
||||
new AtomicBoolean(false);
|
||||
private final AtomicLong nextSendTime = new AtomicLong(Long.MAX_VALUE);
|
||||
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
@@ -99,21 +87,21 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
// Start a query for each type of record
|
||||
generateAck();
|
||||
generateBatch();
|
||||
generateOffer();
|
||||
generateRequest();
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
long now = clock.currentTimeMillis();
|
||||
long nextKeepalive = now + maxIdleTime;
|
||||
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
boolean dataToFlush = true;
|
||||
// Write records until interrupted
|
||||
try {
|
||||
while (!interrupted) {
|
||||
// Work out how long we should wait for a record
|
||||
now = clock.currentTimeMillis();
|
||||
long keepaliveWait = Math.max(0, nextKeepalive - now);
|
||||
long sendWait = Math.max(0, nextSendTime.get() - now);
|
||||
long wait = Math.min(keepaliveWait, sendWait);
|
||||
long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
|
||||
if (wait < 0) wait = 0;
|
||||
// Flush any unflushed data if we're going to wait
|
||||
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
||||
recordWriter.flush();
|
||||
@@ -125,25 +113,20 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
MILLISECONDS);
|
||||
if (task == null) {
|
||||
now = clock.currentTimeMillis();
|
||||
if (now >= nextSendTime.get()) {
|
||||
// Check for retransmittable messages
|
||||
LOG.info("Checking for retransmittable messages");
|
||||
setNextSendTime(Long.MAX_VALUE);
|
||||
generateBatch();
|
||||
generateOffer();
|
||||
if (now >= nextRetxQuery) {
|
||||
// Check for retransmittable records
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
}
|
||||
if (now >= nextKeepalive) {
|
||||
// Flush the stream to keep it alive
|
||||
LOG.info("Sending keepalive");
|
||||
recordWriter.flush();
|
||||
dataToFlush = false;
|
||||
nextKeepalive = now + maxIdleTime;
|
||||
}
|
||||
} else if (task == CLOSE) {
|
||||
LOG.info("Closed");
|
||||
break;
|
||||
} else if (task == NEXT_SEND_TIME_DECREASED) {
|
||||
LOG.info("Next send time decreased");
|
||||
} else {
|
||||
task.run();
|
||||
dataToFlush = true;
|
||||
@@ -159,31 +142,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void generateAck() {
|
||||
if (generateAckQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
}
|
||||
|
||||
private void generateBatch() {
|
||||
if (generateBatchQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
|
||||
private void generateOffer() {
|
||||
if (generateOfferQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
|
||||
private void generateRequest() {
|
||||
if (generateRequestQueued.compareAndSet(false, true))
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
}
|
||||
|
||||
private void setNextSendTime(long time) {
|
||||
long old = nextSendTime.getAndSet(time);
|
||||
if (time < old) writerTasks.add(NEXT_SEND_TIME_DECREASED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
interrupted = true;
|
||||
@@ -196,23 +154,22 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof MessageSharedEvent) {
|
||||
generateOffer();
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||
if (g.getAffectedContacts().contains(contactId))
|
||||
generateOffer();
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
} else if (e instanceof MessageRequestedEvent) {
|
||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||
generateBatch();
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
} else if (e instanceof MessageToAckEvent) {
|
||||
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
||||
generateAck();
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
} else if (e instanceof MessageToRequestEvent) {
|
||||
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
||||
generateRequest();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +179,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateAckQueued.getAndSet(false)) throw new AssertionError();
|
||||
try {
|
||||
Ack a;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
@@ -256,7 +212,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
recordWriter.writeAck(ack);
|
||||
LOG.info("Sent ack");
|
||||
generateAck();
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,15 +222,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateBatchQueued.getAndSet(false))
|
||||
throw new AssertionError();
|
||||
try {
|
||||
Collection<byte[]> b;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
b = db.generateRequestedBatch(txn, contactId,
|
||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -303,7 +256,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
||||
LOG.info("Sent batch");
|
||||
generateBatch();
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,15 +266,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateOfferQueued.getAndSet(false))
|
||||
throw new AssertionError();
|
||||
try {
|
||||
Offer o;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
|
||||
maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -350,7 +300,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
recordWriter.writeOffer(offer);
|
||||
LOG.info("Sent offer");
|
||||
generateOffer();
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,8 +310,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (interrupted) return;
|
||||
if (!generateRequestQueued.getAndSet(false))
|
||||
throw new AssertionError();
|
||||
try {
|
||||
Request r;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
@@ -395,7 +343,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (interrupted) return;
|
||||
recordWriter.writeRequest(request);
|
||||
LOG.info("Sent request");
|
||||
generateRequest();
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
|
||||
/**
|
||||
* An incoming {@link SyncSession}.
|
||||
@@ -97,9 +96,8 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
@@ -28,7 +28,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
@@ -110,9 +109,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +71,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
@Override
|
||||
public void startService() {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
validateOutstandingMessagesAsync();
|
||||
deliverOutstandingMessagesAsync();
|
||||
shareOutstandingMessagesAsync();
|
||||
for (ClientId c : validators.keySet()) {
|
||||
validateOutstandingMessagesAsync(c);
|
||||
deliverOutstandingMessagesAsync(c);
|
||||
shareOutstandingMessagesAsync(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,17 +93,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
hooks.put(c, hook);
|
||||
}
|
||||
|
||||
private void validateOutstandingMessagesAsync() {
|
||||
dbExecutor.execute(this::validateOutstandingMessages);
|
||||
private void validateOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> validateOutstandingMessages(c));
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void validateOutstandingMessages() {
|
||||
private void validateOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> unvalidated = new LinkedList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
unvalidated.addAll(db.getMessagesToValidate(txn));
|
||||
unvalidated.addAll(db.getMessagesToValidate(txn, c));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -146,17 +148,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverOutstandingMessagesAsync() {
|
||||
dbExecutor.execute(this::deliverOutstandingMessages);
|
||||
private void deliverOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> deliverOutstandingMessages(c));
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void deliverOutstandingMessages() {
|
||||
private void deliverOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> pending = new LinkedList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
pending.addAll(db.getPendingMessages(txn));
|
||||
pending.addAll(db.getPendingMessages(txn, c));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -351,17 +353,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
return pending;
|
||||
}
|
||||
|
||||
private void shareOutstandingMessagesAsync() {
|
||||
dbExecutor.execute(this::shareOutstandingMessages);
|
||||
private void shareOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> shareOutstandingMessages(c));
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void shareOutstandingMessages() {
|
||||
private void shareOutstandingMessages(ClientId c) {
|
||||
try {
|
||||
Queue<MessageId> toShare = new LinkedList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
toShare.addAll(db.getMessagesToShare(txn));
|
||||
toShare.addAll(db.getMessagesToShare(txn, c));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
|
||||
@@ -4,9 +4,8 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
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.ScheduledThreadPoolExecutor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -26,10 +25,7 @@ public class SystemModule {
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
public SystemModule() {
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ScheduledThreadPoolExecutor.DiscardPolicy();
|
||||
scheduler = new ScheduledThreadPoolExecutor(1, policy);
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class EcdsaSignatureTest extends SignatureTest {
|
||||
|
||||
@Override
|
||||
protected KeyPair generateKeyPair() {
|
||||
return crypto.generateSignatureKeyPair();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||
throws GeneralSecurityException {
|
||||
return crypto.sign(label, toSign, privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException {
|
||||
return crypto.verify(label, signedData, publicKey, signature);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class EdSignatureTest extends SignatureTest {
|
||||
|
||||
@Override
|
||||
protected KeyPair generateKeyPair() {
|
||||
return crypto.generateEdKeyPair();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||
throws GeneralSecurityException {
|
||||
return crypto.signEd(label, toSign, privateKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException {
|
||||
return crypto.verifyEd(label, signedData, publicKey, signature);
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,32 @@ import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SignatureTest extends BrambleTestCase {
|
||||
public abstract class SignatureTest extends BrambleTestCase {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
protected final CryptoComponent crypto;
|
||||
|
||||
private final byte[] publicKey, privateKey;
|
||||
private final String label = StringUtils.getRandomString(42);
|
||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||
|
||||
public SignatureTest() {
|
||||
protected abstract KeyPair generateKeyPair();
|
||||
|
||||
protected abstract byte[] sign(String label, byte[] toSign,
|
||||
byte[] privateKey) throws GeneralSecurityException;
|
||||
|
||||
protected abstract boolean verify(String label, byte[] signedData,
|
||||
byte[] publicKey, byte[] signature) throws GeneralSecurityException;
|
||||
|
||||
SignatureTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||
KeyPair k = crypto.generateSignatureKeyPair();
|
||||
KeyPair k = generateKeyPair();
|
||||
publicKey = k.getPublic().getEncoded();
|
||||
privateKey = k.getPrivate().getEncoded();
|
||||
}
|
||||
@@ -33,19 +42,19 @@ public class SignatureTest extends BrambleTestCase {
|
||||
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
|
||||
throws Exception {
|
||||
// Calculate the Signature twice - the results should be identical
|
||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = crypto.sign(label, inputBytes, privateKey);
|
||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = sign(label, inputBytes, privateKey);
|
||||
assertArrayEquals(sig1, sig2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
|
||||
// Generate second private key
|
||||
KeyPair k2 = crypto.generateSignatureKeyPair();
|
||||
KeyPair k2 = generateKeyPair();
|
||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||
// Calculate the signature with each key
|
||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = crypto.sign(label, inputBytes, privateKey2);
|
||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = sign(label, inputBytes, privateKey2);
|
||||
assertFalse(Arrays.equals(sig1, sig2));
|
||||
}
|
||||
|
||||
@@ -56,8 +65,8 @@ public class SignatureTest extends BrambleTestCase {
|
||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||
// Calculate the signature with different inputs
|
||||
// the results should be different
|
||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = crypto.sign(label, inputBytes2, privateKey);
|
||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = sign(label, inputBytes2, privateKey);
|
||||
assertFalse(Arrays.equals(sig1, sig2));
|
||||
}
|
||||
|
||||
@@ -68,25 +77,25 @@ public class SignatureTest extends BrambleTestCase {
|
||||
String label2 = StringUtils.getRandomString(42);
|
||||
// Calculate the signature with different inputs
|
||||
// the results should be different
|
||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = crypto.sign(label2, inputBytes, privateKey);
|
||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||
byte[] sig2 = sign(label2, inputBytes, privateKey);
|
||||
assertFalse(Arrays.equals(sig1, sig2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatureVerification() throws Exception {
|
||||
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
||||
assertTrue(crypto.verify(label, inputBytes, publicKey, sig));
|
||||
byte[] sig = sign(label, inputBytes, privateKey);
|
||||
assertTrue(verify(label, inputBytes, publicKey, sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentKeyFailsVerification() throws Exception {
|
||||
// Generate second private key
|
||||
KeyPair k2 = crypto.generateSignatureKeyPair();
|
||||
KeyPair k2 = generateKeyPair();
|
||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||
// calculate the signature with different key, should fail to verify
|
||||
byte[] sig = crypto.sign(label, inputBytes, privateKey2);
|
||||
assertFalse(crypto.verify(label, inputBytes, publicKey, sig));
|
||||
byte[] sig = sign(label, inputBytes, privateKey2);
|
||||
assertFalse(verify(label, inputBytes, publicKey, sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,8 +103,8 @@ public class SignatureTest extends BrambleTestCase {
|
||||
// Generate a second input
|
||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||
// calculate the signature with different input, should fail to verify
|
||||
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
||||
assertFalse(crypto.verify(label, inputBytes2, publicKey, sig));
|
||||
byte[] sig = sign(label, inputBytes, privateKey);
|
||||
assertFalse(verify(label, inputBytes2, publicKey, sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,8 +112,8 @@ public class SignatureTest extends BrambleTestCase {
|
||||
// Generate a second label
|
||||
String label2 = StringUtils.getRandomString(42);
|
||||
// calculate the signature with different label, should fail to verify
|
||||
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
||||
assertFalse(crypto.verify(label2, inputBytes, publicKey, sig));
|
||||
byte[] sig = sign(label, inputBytes, privateKey);
|
||||
assertFalse(verify(label2, inputBytes, publicKey, sig));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,22 +47,19 @@ import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@@ -77,13 +74,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final Database<Object> database = context.mock(Database.class);
|
||||
private final ShutdownManager shutdown =
|
||||
context.mock(ShutdownManager.class);
|
||||
private final EventBus eventBus = context.mock(EventBus.class);
|
||||
public class DatabaseComponentImplTest extends BrambleTestCase {
|
||||
|
||||
private final Object txn = new Object();
|
||||
private final ClientId clientId;
|
||||
@@ -134,11 +125,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSimpleCalls() throws Exception {
|
||||
int shutdownHandle = 12345;
|
||||
Mockery context = new Mockery();
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// open()
|
||||
oneOf(database).open(null);
|
||||
oneOf(database).open();
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
@@ -164,7 +160,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
ContactStatusChangedEvent.class)));
|
||||
// getContacts()
|
||||
oneOf(database).getContacts(txn);
|
||||
will(returnValue(singletonList(contact)));
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
// addGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
@@ -175,12 +171,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
// getGroups()
|
||||
oneOf(database).getGroups(txn, clientId);
|
||||
will(returnValue(singletonList(group)));
|
||||
will(returnValue(Collections.singletonList(group)));
|
||||
// removeGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(emptyMap()));
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(database).removeGroup(txn, groupId);
|
||||
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
@@ -198,23 +194,24 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// close()
|
||||
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
||||
oneOf(database).close();
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.addLocalAuthor(transaction, localAuthor);
|
||||
assertEquals(contactId,
|
||||
db.addContact(transaction, author, localAuthorId, true,
|
||||
true));
|
||||
assertEquals(singletonList(contact),
|
||||
assertEquals(Collections.singletonList(contact),
|
||||
db.getContacts(transaction));
|
||||
db.addGroup(transaction, group); // First time - listeners called
|
||||
db.addGroup(transaction, group); // Second time - not called
|
||||
assertEquals(singletonList(group),
|
||||
assertEquals(Collections.singletonList(group),
|
||||
db.getGroups(transaction, clientId));
|
||||
db.removeGroup(transaction, group);
|
||||
db.removeContact(transaction, contactId);
|
||||
@@ -224,11 +221,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
db.close();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalMessagesAreNotStoredUnlessGroupExists()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -248,10 +252,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLocalMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -259,8 +270,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true);
|
||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// The message was added, so the listeners should be called
|
||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||
@@ -278,11 +294,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfContactIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(18).of(database).startTransaction();
|
||||
@@ -396,7 +419,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
Ack a = new Ack(Collections.singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -417,7 +440,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Offer o = new Offer(singletonList(messageId));
|
||||
Offer o = new Offer(Collections.singletonList(messageId));
|
||||
db.receiveOffer(transaction, contactId, o);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -428,7 +451,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Request r = new Request(singletonList(messageId));
|
||||
Request r = new Request(Collections.singletonList(messageId));
|
||||
db.receiveRequest(transaction, contactId, r);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -477,11 +500,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the pseudonym is in the DB (which it's not)
|
||||
exactly(3).of(database).startTransaction();
|
||||
@@ -522,11 +552,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfGroupIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the group is in the DB (which it's not)
|
||||
exactly(8).of(database).startTransaction();
|
||||
@@ -620,11 +657,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfMessageIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the message is in the DB (which it's not)
|
||||
exactly(11).of(database).startTransaction();
|
||||
@@ -748,11 +792,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariousMethodsThrowExceptionIfTransportIsMissing()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -839,12 +890,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateAck() throws Exception {
|
||||
Collection<MessageId> messagesToAck = Arrays.asList(messageId,
|
||||
messageId1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -867,6 +925,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -874,6 +934,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
byte[] raw1 = new byte[size];
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -904,12 +969,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateOffer() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -935,12 +1007,19 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateRequest() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -963,6 +1042,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -970,6 +1051,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
byte[] raw1 = new byte[size];
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1001,10 +1087,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveAck() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1021,16 +1114,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
Ack a = new Ack(Collections.singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1041,7 +1141,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(VISIBLE));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, false);
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, true, true);
|
||||
// Second time
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
@@ -1070,10 +1175,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveDuplicateMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1100,10 +1212,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveMessageWithoutVisibleGroup() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1123,6 +1242,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1130,6 +1251,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1170,10 +1296,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveRequest() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1191,20 +1324,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
Request r = new Request(singletonList(messageId));
|
||||
Request r = new Request(Collections.singletonList(messageId));
|
||||
db.receiveRequest(transaction, contactId, r);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
|
||||
throws Exception {
|
||||
AtomicReference<GroupVisibilityUpdatedEvent> event =
|
||||
new AtomicReference<>();
|
||||
|
||||
public void testChangingVisibilityCallsListeners() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1213,13 +1349,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, contactId, groupId);
|
||||
will(returnValue(INVISIBLE));
|
||||
will(returnValue(INVISIBLE)); // Not yet visible
|
||||
oneOf(database).addGroupVisibility(txn, contactId, groupId, false);
|
||||
oneOf(database).getMessageIds(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(messageId)));
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
will(new CaptureArgumentAction<>(event,
|
||||
GroupVisibilityUpdatedEvent.class, 0));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -1232,52 +1371,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
GroupVisibilityUpdatedEvent e = event.get();
|
||||
assertNotNull(e);
|
||||
assertEquals(singletonList(contactId), e.getAffectedContacts());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingVisibilityFromVisibleToInvisibleCallsListeners()
|
||||
throws Exception {
|
||||
AtomicReference<GroupVisibilityUpdatedEvent> event =
|
||||
new AtomicReference<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, contactId, groupId);
|
||||
will(returnValue(VISIBLE));
|
||||
oneOf(database).removeGroupVisibility(txn, contactId, groupId);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
will(new CaptureArgumentAction<>(event,
|
||||
GroupVisibilityUpdatedEvent.class, 0));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.setGroupVisibility(transaction, contactId, groupId, INVISIBLE);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
GroupVisibilityUpdatedEvent e = event.get();
|
||||
assertNotNull(e);
|
||||
assertEquals(singletonList(contactId), e.getAffectedContacts());
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotChangingVisibilityDoesNotCallListeners()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1299,13 +1403,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys();
|
||||
Map<ContactId, TransportKeys> keys =
|
||||
singletonMap(contactId, transportKeys);
|
||||
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
||||
contactId, transportKeys);
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1335,6 +1446,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys() {
|
||||
@@ -1367,6 +1480,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
Settings merged = new Settings();
|
||||
merged.put("foo", "bar");
|
||||
merged.put("baz", "qux");
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1396,6 +1514,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@@ -1425,6 +1545,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
private void testCannotStartTransactionDuringTransaction(
|
||||
boolean firstTxnReadOnly, boolean secondTxnReadOnly)
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1434,12 +1560,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
shutdown);
|
||||
|
||||
assertNotNull(db.startTransaction(firstTxnReadOnly));
|
||||
db.startTransaction(secondTxnReadOnly);
|
||||
fail();
|
||||
try {
|
||||
db.startTransaction(secondTxnReadOnly);
|
||||
fail();
|
||||
} finally {
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotAddLocalIdentityAsContact() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1463,10 +1599,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCannotAddDuplicateContact() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
@SuppressWarnings("unchecked")
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1492,16 +1636,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMessageDependencies() throws Exception {
|
||||
int shutdownHandle = 12345;
|
||||
Mockery context = new Mockery();
|
||||
Database<Object> database = context.mock(Database.class);
|
||||
ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
||||
EventBus eventBus = context.mock(EventBus.class);
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
context.checking(new Expectations() {{
|
||||
// open()
|
||||
oneOf(database).open(null);
|
||||
oneOf(database).open();
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
@@ -1513,8 +1663,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true);
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||
// addMessageDependencies()
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
@@ -1538,12 +1693,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// close()
|
||||
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
||||
oneOf(database).close();
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.addLocalMessage(transaction, message, metadata, true);
|
||||
@@ -1558,5 +1714,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
db.close();
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.system.SystemClock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
|
||||
import static org.briarproject.bramble.db.JdbcDatabase.CODE_SCHEMA_VERSION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@NotNullByDefault
|
||||
public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
@SuppressWarnings("unchecked")
|
||||
private final Migration<Connection> migration =
|
||||
context.mock(Migration.class, "migration");
|
||||
@SuppressWarnings("unchecked")
|
||||
private final Migration<Connection> migration1 =
|
||||
context.mock(Migration.class, "migration1");
|
||||
|
||||
protected final DatabaseConfig config =
|
||||
new TestDatabaseConfig(testDir, 1024 * 1024);
|
||||
protected final Clock clock = new SystemClock();
|
||||
|
||||
abstract Database<Connection> createDatabase(
|
||||
List<Migration<Connection>> migrations) throws Exception;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
assertTrue(testDir.mkdirs());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotRunMigrationsWhenCreatingDatabase()
|
||||
throws Exception {
|
||||
Database<Connection> db = createDatabase(singletonList(migration));
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test(expected = DbException.class)
|
||||
public void testThrowsExceptionIfDataSchemaVersionIsMissing()
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, -1);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotRunMigrationsIfSchemaVersionsMatch()
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
// Reopen the DB - migrations should not be run
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
assertTrue(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test(expected = DataTooNewException.class)
|
||||
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test(expected = DataTooOldException.class)
|
||||
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoMigrations()
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(emptyList());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(emptyList());
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test(expected = DataTooOldException.class)
|
||||
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoSuitableMigration()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(migration).getStartVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||
oneOf(migration).getEndVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||
oneOf(migration1).getStartVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||
oneOf(migration1).getEndVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION));
|
||||
}});
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunsMigrationIfCodeIsNewerThanDataAndSuitableMigration()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// First migration should be run, increasing schema version by 2
|
||||
oneOf(migration).getStartVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||
oneOf(migration).getEndVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION));
|
||||
oneOf(migration).migrate(with(any(Connection.class)));
|
||||
// Second migration is not suitable and should be skipped
|
||||
oneOf(migration1).getStartVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||
oneOf(migration1).getEndVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION));
|
||||
}});
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||
db.close();
|
||||
// Reopen the DB - the first migration should be run
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
assertTrue(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunsMigrationsIfCodeIsNewerThanDataAndSuitableMigrations()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// First migration should be run, incrementing schema version
|
||||
oneOf(migration).getStartVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||
oneOf(migration).getEndVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||
oneOf(migration).migrate(with(any(Connection.class)));
|
||||
// Second migration should be run, incrementing schema version again
|
||||
oneOf(migration1).getStartVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||
oneOf(migration1).getEndVersion();
|
||||
will(returnValue(CODE_SCHEMA_VERSION));
|
||||
oneOf(migration1).migrate(with(any(Connection.class)));
|
||||
}});
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||
db.close();
|
||||
// Reopen the DB - both migrations should be run
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
assertTrue(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
|
||||
private int getDataSchemaVersion(Database<Connection> db)
|
||||
throws Exception {
|
||||
Connection txn = db.startTransaction();
|
||||
Settings s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
db.commitTransaction(txn);
|
||||
return s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
}
|
||||
|
||||
private void setDataSchemaVersion(Database<Connection> db, int version)
|
||||
throws Exception {
|
||||
Settings s = new Settings();
|
||||
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||
Connection txn = db.startTransaction();
|
||||
db.mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||
db.commitTransaction(txn);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
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.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
@@ -16,7 +17,6 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
@@ -24,6 +24,7 @@ import org.briarproject.bramble.system.SystemClock;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -43,6 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
@@ -52,12 +54,6 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -77,6 +73,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
private final ClientId clientId;
|
||||
private final Group group;
|
||||
private final Author author;
|
||||
private final AuthorId localAuthorId;
|
||||
private final LocalAuthor localAuthor;
|
||||
private final MessageId messageId;
|
||||
private final long timestamp;
|
||||
@@ -87,16 +84,19 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
private final ContactId contactId;
|
||||
|
||||
public H2DatabaseTest() throws Exception {
|
||||
groupId = new GroupId(getRandomId());
|
||||
clientId = new ClientId(getRandomString(123));
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
clientId = new ClientId(StringUtils.getRandomString(5));
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
author = getAuthor();
|
||||
localAuthor = getLocalAuthor();
|
||||
messageId = new MessageId(getRandomId());
|
||||
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
localAuthorId = new AuthorId(TestUtils.getRandomId());
|
||||
timestamp = System.currentTimeMillis();
|
||||
localAuthor = new LocalAuthor(localAuthorId, "Bob",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
|
||||
messageId = new MessageId(TestUtils.getRandomId());
|
||||
size = 1234;
|
||||
raw = getRandomBytes(size);
|
||||
raw = TestUtils.getRandomBytes(size);
|
||||
message = new Message(messageId, groupId, timestamp, raw);
|
||||
transportId = new TransportId("id");
|
||||
contactId = new ContactId(1);
|
||||
@@ -114,14 +114,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
assertTrue(db.containsContact(txn, contactId));
|
||||
assertFalse(db.containsGroup(txn, groupId));
|
||||
db.addGroup(txn, group);
|
||||
assertTrue(db.containsGroup(txn, groupId));
|
||||
assertFalse(db.containsMessage(txn, messageId));
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -159,7 +159,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
|
||||
// Removing the group should remove the message
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
@@ -177,15 +177,22 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
|
||||
// The contact has not seen the message, so it should be sendable
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
// The message has no status yet, so it should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// Adding a status with seen = false should make the message sendable
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
@@ -208,11 +215,12 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared but unvalidated message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, UNKNOWN, true, null);
|
||||
db.addMessage(txn, message, UNKNOWN, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The message has not been validated, so it should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -253,10 +261,11 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, an invisible group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The group is invisible, so the message should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -304,11 +313,12 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and an unshared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, false, null);
|
||||
db.addMessage(txn, message, DELIVERED, false);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The message is not shared, so it should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -335,11 +345,12 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The message is sendable, but too large to send
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -361,16 +372,20 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact and a visible group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
|
||||
// Add some messages to ack
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
db.addMessage(txn, message, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, true);
|
||||
db.raiseAckFlag(txn, contactId, messageId);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId1, false, true);
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
|
||||
// Both message IDs should be returned
|
||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
@@ -383,14 +398,6 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
|
||||
contactId, 1234));
|
||||
|
||||
// Raise the ack flag again
|
||||
db.raiseAckFlag(txn, contactId, messageId);
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
|
||||
// Both message IDs should be returned
|
||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
assertEquals(Arrays.asList(messageId, messageId1), ids);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
@@ -402,11 +409,12 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// Retrieve the message from the database and mark it as sent
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -447,7 +455,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Storing a message should reduce the free space
|
||||
Connection txn = db.startTransaction();
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.commitTransaction(txn);
|
||||
assertTrue(db.getFreeSpace() < free);
|
||||
|
||||
@@ -559,7 +567,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact and a shared group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
@@ -579,7 +587,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
|
||||
// The group is not in the database
|
||||
@@ -595,14 +603,15 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, an invisible group and a message
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The group is not visible so the message should not be visible
|
||||
// The group is not visible
|
||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -616,37 +625,37 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact and a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// The group should not be visible to the contact
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.emptyMap(),
|
||||
assertEquals(Collections.emptyList(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Make the group visible to the contact
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Share the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonMap(contactId, true),
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Unshare the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Make the group invisible again
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.emptyMap(),
|
||||
assertEquals(Collections.emptyList(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -666,7 +675,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the contact, the transport and the transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.addTransportKeys(txn, contactId, keys);
|
||||
@@ -728,7 +737,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the contact, transport and transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||
@@ -764,7 +773,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the contact, transport and transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||
@@ -799,7 +808,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
|
||||
// Add a contact associated with the local author
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
|
||||
// Ensure contact is returned from database by Author ID
|
||||
@@ -824,19 +833,18 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a local author - no contacts should be associated
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
Collection<ContactId> contacts =
|
||||
db.getContacts(txn, localAuthor.getId());
|
||||
Collection<ContactId> contacts = db.getContacts(txn, localAuthorId);
|
||||
assertEquals(Collections.emptyList(), contacts);
|
||||
|
||||
// Add a contact associated with the local author
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
contacts = db.getContacts(txn, localAuthor.getId());
|
||||
contacts = db.getContacts(txn, localAuthorId);
|
||||
assertEquals(Collections.singletonList(contactId), contacts);
|
||||
|
||||
// Remove the local author - the contact should be removed
|
||||
db.removeLocalAuthor(txn, localAuthor.getId());
|
||||
contacts = db.getContacts(txn, localAuthor.getId());
|
||||
db.removeLocalAuthor(txn, localAuthorId);
|
||||
contacts = db.getContacts(txn, localAuthorId);
|
||||
assertEquals(Collections.emptyList(), contacts);
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
|
||||
@@ -851,14 +859,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact - initially there should be no offered messages
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
assertEquals(0, db.countOfferedMessages(txn, contactId));
|
||||
|
||||
// Add some offered messages and count them
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
MessageId m = new MessageId(getRandomId());
|
||||
MessageId m = new MessageId(TestUtils.getRandomId());
|
||||
db.addOfferedMessage(txn, contactId, m);
|
||||
ids.add(m);
|
||||
}
|
||||
@@ -867,7 +875,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Remove some of the offered messages and count again
|
||||
List<MessageId> half = ids.subList(0, 5);
|
||||
db.removeOfferedMessages(txn, contactId, half);
|
||||
assertEquals(5, db.countOfferedMessages(txn, contactId));
|
||||
assertTrue(db.removeOfferedMessage(txn, contactId, ids.get(5)));
|
||||
assertEquals(4, db.countOfferedMessages(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -918,7 +927,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -989,7 +998,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1042,7 +1051,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMetadataQueries() throws Exception {
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1050,8 +1059,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and two messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1146,7 +1155,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMetadataQueriesOnlyForDeliveredMessages() throws Exception {
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1154,8 +1163,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and two messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1217,10 +1226,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMessageDependencies() throws Exception {
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId2 = new MessageId(getRandomId());
|
||||
MessageId messageId3 = new MessageId(getRandomId());
|
||||
MessageId messageId4 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
Message message2 = new Message(messageId2, groupId, timestamp, raw);
|
||||
|
||||
@@ -1229,9 +1238,9 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and some messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message2, INVALID, true, contactId);
|
||||
db.addMessage(txn, message, PENDING, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message2, INVALID, true);
|
||||
|
||||
// Add dependencies
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
@@ -1298,26 +1307,26 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
db.addMessage(txn, message, PENDING, true);
|
||||
|
||||
// Add a second group
|
||||
GroupId groupId1 = new GroupId(getRandomId());
|
||||
GroupId groupId1 = new GroupId(TestUtils.getRandomId());
|
||||
Group group1 = new Group(groupId1, clientId,
|
||||
getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
|
||||
TestUtils.getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
|
||||
db.addGroup(txn, group1);
|
||||
|
||||
// Add a message to the second group
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId1, timestamp, raw);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
|
||||
// Create an ID for a missing message
|
||||
MessageId messageId2 = new MessageId(getRandomId());
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
|
||||
// Add another message to the first group
|
||||
MessageId messageId3 = new MessageId(getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
Message message3 = new Message(messageId3, groupId, timestamp, raw);
|
||||
db.addMessage(txn, message3, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message3, DELIVERED, true);
|
||||
|
||||
// Add dependencies between the messages
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
@@ -1350,10 +1359,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetPendingMessagesForDelivery() throws Exception {
|
||||
MessageId mId1 = new MessageId(getRandomId());
|
||||
MessageId mId2 = new MessageId(getRandomId());
|
||||
MessageId mId3 = new MessageId(getRandomId());
|
||||
MessageId mId4 = new MessageId(getRandomId());
|
||||
MessageId mId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId4 = new MessageId(TestUtils.getRandomId());
|
||||
Message m1 = new Message(mId1, groupId, timestamp, raw);
|
||||
Message m2 = new Message(mId2, groupId, timestamp, raw);
|
||||
Message m3 = new Message(mId3, groupId, timestamp, raw);
|
||||
@@ -1364,20 +1373,20 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and some messages with different states
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, m1, UNKNOWN, true, contactId);
|
||||
db.addMessage(txn, m2, INVALID, true, contactId);
|
||||
db.addMessage(txn, m3, PENDING, true, contactId);
|
||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, m1, UNKNOWN, true);
|
||||
db.addMessage(txn, m2, INVALID, true);
|
||||
db.addMessage(txn, m3, PENDING, true);
|
||||
db.addMessage(txn, m4, DELIVERED, true);
|
||||
|
||||
Collection<MessageId> result;
|
||||
|
||||
// Retrieve messages to be validated
|
||||
result = db.getMessagesToValidate(txn);
|
||||
result = db.getMessagesToValidate(txn, clientId);
|
||||
assertEquals(1, result.size());
|
||||
assertTrue(result.contains(mId1));
|
||||
|
||||
// Retrieve pending messages
|
||||
result = db.getPendingMessages(txn);
|
||||
result = db.getPendingMessages(txn, clientId);
|
||||
assertEquals(1, result.size());
|
||||
assertTrue(result.contains(mId3));
|
||||
|
||||
@@ -1387,10 +1396,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetMessagesToShare() throws Exception {
|
||||
MessageId mId1 = new MessageId(getRandomId());
|
||||
MessageId mId2 = new MessageId(getRandomId());
|
||||
MessageId mId3 = new MessageId(getRandomId());
|
||||
MessageId mId4 = new MessageId(getRandomId());
|
||||
MessageId mId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId4 = new MessageId(TestUtils.getRandomId());
|
||||
Message m1 = new Message(mId1, groupId, timestamp, raw);
|
||||
Message m2 = new Message(mId2, groupId, timestamp, raw);
|
||||
Message m3 = new Message(mId3, groupId, timestamp, raw);
|
||||
@@ -1401,10 +1410,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and some messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, m1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, m2, DELIVERED, false, contactId);
|
||||
db.addMessage(txn, m3, DELIVERED, false, contactId);
|
||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, m1, DELIVERED, true);
|
||||
db.addMessage(txn, m2, DELIVERED, false);
|
||||
db.addMessage(txn, m3, DELIVERED, false);
|
||||
db.addMessage(txn, m4, DELIVERED, true);
|
||||
|
||||
// Introduce dependencies between the messages
|
||||
db.addMessageDependency(txn, groupId, mId1, mId2);
|
||||
@@ -1412,7 +1421,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
db.addMessageDependency(txn, groupId, mId4, mId3);
|
||||
|
||||
// Retrieve messages to be shared
|
||||
Collection<MessageId> result = db.getMessagesToShare(txn);
|
||||
Collection<MessageId> result =
|
||||
db.getMessagesToShare(txn, clientId);
|
||||
assertEquals(2, result.size());
|
||||
assertTrue(result.contains(mId2));
|
||||
assertTrue(result.contains(mId3));
|
||||
@@ -1428,11 +1438,12 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The message should not be sent or seen
|
||||
MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
|
||||
@@ -1496,7 +1507,9 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testDifferentLocalAuthorsCanHaveTheSameContact()
|
||||
throws Exception {
|
||||
LocalAuthor localAuthor1 = getLocalAuthor();
|
||||
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
LocalAuthor localAuthor1 = new LocalAuthor(localAuthorId1, "Carol",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
@@ -1507,15 +1520,15 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the same contact for each local author
|
||||
ContactId contactId =
|
||||
db.addContact(txn, author, localAuthor.getId(), true, true);
|
||||
db.addContact(txn, author, localAuthorId, true, true);
|
||||
ContactId contactId1 =
|
||||
db.addContact(txn, author, localAuthor1.getId(), true, true);
|
||||
db.addContact(txn, author, localAuthorId1, true, true);
|
||||
|
||||
// The contacts should be distinct
|
||||
assertNotEquals(contactId, contactId1);
|
||||
assertEquals(2, db.getContacts(txn).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthor.getId()).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthor1.getId()).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthorId).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthorId1).size());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -1528,11 +1541,12 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
|
||||
// The message should be visible to the contact
|
||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
@@ -1573,7 +1587,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
|
||||
// The contact should be active
|
||||
@@ -1606,7 +1620,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, UNKNOWN, false, contactId);
|
||||
db.addMessage(txn, message, UNKNOWN, false);
|
||||
|
||||
// Walk the message through the validation and delivery states
|
||||
assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
|
||||
@@ -1621,56 +1635,6 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNextSendTime() throws Exception {
|
||||
long now = System.currentTimeMillis();
|
||||
Database<Connection> db = open(false, new StoppedClock(now));
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, UNKNOWN, false, null);
|
||||
|
||||
// There should be no messages to send
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Share the group with the contact - still no messages to send
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Set the message's state to DELIVERED - still no messages to send
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Share the message - now it should be sendable immediately
|
||||
db.setMessageShared(txn, messageId);
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Mark the message as requested - it should still be sendable
|
||||
db.raiseRequestedFlag(txn, contactId, messageId);
|
||||
assertEquals(0, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Update the message's expiry time as though we sent it - now the
|
||||
// message should be sendable after one round-trip
|
||||
db.updateExpiryTime(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Update the message's expiry time again - now it should be sendable
|
||||
// after two round-trips
|
||||
db.updateExpiryTime(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Delete the message - there should be no messages to send
|
||||
db.deleteMessage(txn, messageId);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionHandling() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1688,33 +1652,28 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
private Database<Connection> open(boolean resume) throws Exception {
|
||||
return open(resume, new SystemClock());
|
||||
}
|
||||
|
||||
private Database<Connection> open(boolean resume, Clock clock)
|
||||
throws Exception {
|
||||
Database<Connection> db = new H2Database(new TestDatabaseConfig(testDir,
|
||||
MAX_SIZE), clock);
|
||||
MAX_SIZE), new SystemClock());
|
||||
if (!resume) TestUtils.deleteTestDirectory(testDir);
|
||||
db.open(null);
|
||||
db.open();
|
||||
return db;
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys() {
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
SecretKey inPrevTagKey = TestUtils.getSecretKey();
|
||||
SecretKey inPrevHeaderKey = TestUtils.getSecretKey();
|
||||
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||
1, 123, new byte[4]);
|
||||
SecretKey inCurrTagKey = getSecretKey();
|
||||
SecretKey inCurrHeaderKey = getSecretKey();
|
||||
SecretKey inCurrTagKey = TestUtils.getSecretKey();
|
||||
SecretKey inCurrHeaderKey = TestUtils.getSecretKey();
|
||||
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||
2, 234, new byte[4]);
|
||||
SecretKey inNextTagKey = getSecretKey();
|
||||
SecretKey inNextHeaderKey = getSecretKey();
|
||||
SecretKey inNextTagKey = TestUtils.getSecretKey();
|
||||
SecretKey inNextHeaderKey = TestUtils.getSecretKey();
|
||||
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||
3, 345, new byte[4]);
|
||||
SecretKey outCurrTagKey = getSecretKey();
|
||||
SecretKey outCurrHeaderKey = getSecretKey();
|
||||
SecretKey outCurrTagKey = TestUtils.getSecretKey();
|
||||
SecretKey outCurrHeaderKey = TestUtils.getSecretKey();
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||
2, 456);
|
||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
@@ -1724,23 +1683,4 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
private static class StoppedClock implements Clock {
|
||||
|
||||
private final long time;
|
||||
|
||||
private StoppedClock(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentTimeMillis() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sleep(long milliseconds) throws InterruptedException {
|
||||
Thread.sleep(milliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
|
||||
@NotNullByDefault
|
||||
public class H2MigrationTest extends DatabaseMigrationTest {
|
||||
|
||||
@Override
|
||||
Database<Connection> createDatabase(List<Migration<Connection>> migrations)
|
||||
throws Exception {
|
||||
return new H2Database(config, clock) {
|
||||
@Override
|
||||
List<Migration<Connection>> getMigrations() {
|
||||
return migrations;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class Migration30_31Test extends BrambleTestCase {
|
||||
|
||||
private static final String CREATE_GROUPS_STUB =
|
||||
"CREATE TABLE groups"
|
||||
+ " (groupId BINARY(32) NOT NULL,"
|
||||
+ " PRIMARY KEY (groupId))";
|
||||
|
||||
private static final String CREATE_MESSAGES =
|
||||
"CREATE TABLE messages"
|
||||
+ " (messageId BINARY(32) NOT NULL,"
|
||||
+ " groupId BINARY(32) NOT NULL,"
|
||||
+ " timestamp BIGINT NOT NULL,"
|
||||
+ " state INT NOT NULL,"
|
||||
+ " shared BOOLEAN NOT NULL,"
|
||||
+ " length INT NOT NULL,"
|
||||
+ " raw BLOB," // Null if message has been deleted
|
||||
+ " PRIMARY KEY (messageId),"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_MESSAGE_METADATA_30 =
|
||||
"CREATE TABLE messageMetadata"
|
||||
+ " (messageId BINARY(32) NOT NULL,"
|
||||
+ " key VARCHAR NOT NULL,"
|
||||
+ " value BINARY NOT NULL,"
|
||||
+ " PRIMARY KEY (messageId, key),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File db = new File(testDir, "db");
|
||||
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
||||
private final GroupId groupId = new GroupId(getRandomId());
|
||||
private final GroupId groupId1 = new GroupId(getRandomId());
|
||||
private final Message message = getMessage(groupId);
|
||||
private final Message message1 = getMessage(groupId1);
|
||||
private final Metadata meta = new Metadata(), meta1 = new Metadata();
|
||||
|
||||
private Connection connection = null;
|
||||
|
||||
public Migration30_31Test() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
meta.put(getRandomString(123 + i), getRandomBytes(123 + i));
|
||||
meta1.put(getRandomString(123 + i), getRandomBytes(123 + i));
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
assertTrue(testDir.mkdirs());
|
||||
Class.forName("org.h2.Driver");
|
||||
connection = DriverManager.getConnection(url);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (connection != null) connection.close();
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMigration() throws Exception {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
s.execute(CREATE_GROUPS_STUB);
|
||||
s.execute(CREATE_MESSAGES);
|
||||
s.execute(CREATE_MESSAGE_METADATA_30);
|
||||
s.close();
|
||||
|
||||
addGroup(groupId);
|
||||
addMessage(message, DELIVERED, true);
|
||||
addMessageMetadata30(message, meta);
|
||||
assertMetadataEquals(meta, getMessageMetadata(message.getId()));
|
||||
|
||||
addGroup(groupId1);
|
||||
addMessage(message1, UNKNOWN, false);
|
||||
addMessageMetadata30(message1, meta1);
|
||||
assertMetadataEquals(meta1, getMessageMetadata(message1.getId()));
|
||||
|
||||
new Migration30_31().migrate(connection);
|
||||
|
||||
assertMetadataEquals(meta, getMessageMetadata(message.getId()));
|
||||
for (String key : meta.keySet()) {
|
||||
GroupId g = getMessageMetadataGroupId31(message.getId(), key);
|
||||
assertEquals(groupId, g);
|
||||
State state = getMessageMetadataState31(message.getId(), key);
|
||||
assertEquals(DELIVERED, state);
|
||||
}
|
||||
|
||||
assertMetadataEquals(meta1, getMessageMetadata(message1.getId()));
|
||||
for (String key : meta1.keySet()) {
|
||||
GroupId g = getMessageMetadataGroupId31(message1.getId(), key);
|
||||
assertEquals(groupId1, g);
|
||||
State state = getMessageMetadataState31(message1.getId(), key);
|
||||
assertEquals(UNKNOWN, state);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addGroup(GroupId g) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groups (groupId) VALUES (?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addMessage(Message m, State state, boolean shared)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||
+ " state, shared, length, raw)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getId().getBytes());
|
||||
ps.setBytes(2, m.getGroupId().getBytes());
|
||||
ps.setLong(3, m.getTimestamp());
|
||||
ps.setInt(4, state.getValue());
|
||||
ps.setBoolean(5, shared);
|
||||
byte[] raw = m.getRaw();
|
||||
ps.setInt(6, raw.length);
|
||||
ps.setBytes(7, raw);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addMessageMetadata30(Message m, Metadata meta)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO messageMetadata"
|
||||
+ " (messageId, key, value)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getId().getBytes());
|
||||
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||
ps.setString(2, e.getKey());
|
||||
ps.setBytes(3, e.getValue());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != meta.size())
|
||||
throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private Metadata getMessageMetadata(MessageId m) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT key, value FROM messageMetadata"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
Metadata meta = new Metadata();
|
||||
while (rs.next()) meta.put(rs.getString(1), rs.getBytes(2));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return meta;
|
||||
} catch (SQLException e) {
|
||||
if (rs != null) rs.close();
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private GroupId getMessageMetadataGroupId31(MessageId m, String key)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT groupId FROM messageMetadata"
|
||||
+ " WHERE messageId = ? AND key = ?";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setString(2, key);
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId g = new GroupId(rs.getBytes(1));
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return g;
|
||||
} catch (SQLException e) {
|
||||
if (rs != null) rs.close();
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private State getMessageMetadataState31(MessageId m, String key)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT state FROM messageMetadata"
|
||||
+ " WHERE messageId = ? AND key = ?";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setString(2, key);
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
State state = State.fromValue(rs.getInt(1));
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return state;
|
||||
} catch (SQLException e) {
|
||||
if (rs != null) rs.close();
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertMetadataEquals(Metadata expected, Metadata actual) {
|
||||
assertEquals(expected.size(), actual.size());
|
||||
for (Entry<String, byte[]> e : expected.entrySet()) {
|
||||
byte[] value = actual.get(e.getKey());
|
||||
assertNotNull(value);
|
||||
assertArrayEquals(e.getValue(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,369 +0,0 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import static java.sql.Types.BINARY;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class Migration31_32Test extends BrambleTestCase {
|
||||
|
||||
private static final String CREATE_GROUPS_STUB =
|
||||
"CREATE TABLE groups"
|
||||
+ " (groupId BINARY(32) NOT NULL,"
|
||||
+ " PRIMARY KEY (groupId))";
|
||||
|
||||
private static final String CREATE_CONTACTS_STUB =
|
||||
"CREATE TABLE contacts"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " PRIMARY KEY (contactId))";
|
||||
|
||||
private static final String CREATE_GROUP_VISIBILITIES_STUB =
|
||||
"CREATE TABLE groupVisibilities"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " groupId BINARY(32) NOT NULL,"
|
||||
+ " shared BOOLEAN NOT NULL,"
|
||||
+ " PRIMARY KEY (contactId, groupId))";
|
||||
|
||||
private static final String CREATE_MESSAGES =
|
||||
"CREATE TABLE messages"
|
||||
+ " (messageId BINARY(32) NOT NULL,"
|
||||
+ " groupId BINARY(32) NOT NULL,"
|
||||
+ " timestamp BIGINT NOT NULL,"
|
||||
+ " state INT NOT NULL,"
|
||||
+ " shared BOOLEAN NOT NULL,"
|
||||
+ " length INT NOT NULL,"
|
||||
+ " raw BLOB," // Null if message has been deleted
|
||||
+ " PRIMARY KEY (messageId),"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_STATUSES_31 =
|
||||
"CREATE TABLE statuses"
|
||||
+ " (messageId BINARY(32) NOT NULL,"
|
||||
+ " contactId INT NOT NULL,"
|
||||
+ " ack BOOLEAN NOT NULL,"
|
||||
+ " seen BOOLEAN NOT NULL,"
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
+ " expiry BIGINT NOT NULL,"
|
||||
+ " txCount INT NOT NULL,"
|
||||
+ " PRIMARY KEY (messageId, contactId),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File db = new File(testDir, "db");
|
||||
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
||||
private final GroupId groupId = new GroupId(getRandomId());
|
||||
private final GroupId groupId1 = new GroupId(getRandomId());
|
||||
private final ContactId contactId = new ContactId(123);
|
||||
private final ContactId contactId1 = new ContactId(234);
|
||||
private final Message message = getMessage(groupId);
|
||||
private final Message message1 = getMessage(groupId1);
|
||||
private final Message message2 = getMessage(groupId1);
|
||||
|
||||
private Connection connection = null;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
assertTrue(testDir.mkdirs());
|
||||
Class.forName("org.h2.Driver");
|
||||
connection = DriverManager.getConnection(url);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (connection != null) connection.close();
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMigration() throws Exception {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
s.execute(CREATE_GROUPS_STUB);
|
||||
s.execute(CREATE_CONTACTS_STUB);
|
||||
s.execute(CREATE_GROUP_VISIBILITIES_STUB);
|
||||
s.execute(CREATE_MESSAGES);
|
||||
s.execute(CREATE_STATUSES_31);
|
||||
s.close();
|
||||
|
||||
addGroup(groupId);
|
||||
addMessage(message, DELIVERED, true, false);
|
||||
|
||||
addGroup(groupId1);
|
||||
addMessage(message1, UNKNOWN, false, false);
|
||||
addMessage(message2, DELIVERED, true, true);
|
||||
|
||||
addContact(contactId);
|
||||
|
||||
addGroupVisibility(contactId, groupId, true);
|
||||
addStatus31(message.getId(), contactId);
|
||||
|
||||
addGroupVisibility(contactId, groupId1, false);
|
||||
addStatus31(message1.getId(), contactId);
|
||||
addStatus31(message2.getId(), contactId);
|
||||
|
||||
addContact(contactId1);
|
||||
|
||||
addGroupVisibility(contactId1, groupId1, true);
|
||||
addStatus31(message1.getId(), contactId1);
|
||||
addStatus31(message2.getId(), contactId1);
|
||||
|
||||
new Migration31_32().migrate(connection);
|
||||
|
||||
assertTrue(containsStatus(message.getId(), contactId));
|
||||
Status32 status = getStatus32(message.getId(), contactId);
|
||||
assertEquals(groupId, status.groupId);
|
||||
assertEquals(message.getTimestamp(), status.timestamp);
|
||||
assertEquals(message.getLength(), status.length);
|
||||
assertEquals(DELIVERED, status.state);
|
||||
assertTrue(status.groupShared);
|
||||
assertTrue(status.messageShared);
|
||||
assertFalse(status.deleted);
|
||||
|
||||
assertTrue(containsStatus(message1.getId(), contactId));
|
||||
status = getStatus32(message1.getId(), contactId);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message1.getTimestamp(), status.timestamp);
|
||||
assertEquals(message1.getLength(), status.length);
|
||||
assertEquals(UNKNOWN, status.state);
|
||||
assertFalse(status.groupShared);
|
||||
assertFalse(status.messageShared);
|
||||
assertFalse(status.deleted);
|
||||
|
||||
assertTrue(containsStatus(message2.getId(), contactId));
|
||||
status = getStatus32(message2.getId(), contactId);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message2.getTimestamp(), status.timestamp);
|
||||
assertEquals(message2.getLength(), status.length);
|
||||
assertEquals(DELIVERED, status.state);
|
||||
assertFalse(status.groupShared);
|
||||
assertTrue(status.messageShared);
|
||||
assertTrue(status.deleted);
|
||||
|
||||
assertFalse(containsStatus(message.getId(), contactId1));
|
||||
|
||||
assertTrue(containsStatus(message1.getId(), contactId1));
|
||||
status = getStatus32(message1.getId(), contactId1);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message1.getTimestamp(), status.timestamp);
|
||||
assertEquals(message1.getLength(), status.length);
|
||||
assertEquals(UNKNOWN, status.state);
|
||||
assertTrue(status.groupShared);
|
||||
assertFalse(status.messageShared);
|
||||
assertFalse(status.deleted);
|
||||
|
||||
assertTrue(containsStatus(message2.getId(), contactId1));
|
||||
status = getStatus32(message2.getId(), contactId1);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message2.getTimestamp(), status.timestamp);
|
||||
assertEquals(message2.getLength(), status.length);
|
||||
assertEquals(DELIVERED, status.state);
|
||||
assertTrue(status.groupShared);
|
||||
assertTrue(status.messageShared);
|
||||
assertTrue(status.deleted);
|
||||
} catch (SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addGroup(GroupId g) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groups (groupId) VALUES (?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addContact(ContactId c) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO contacts (contactId) VALUES (?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addGroupVisibility(ContactId c, GroupId g, boolean shared)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groupVisibilities"
|
||||
+ " (contactId, groupId, shared) VALUES (?, ?, ?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setBoolean(3, shared);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addMessage(Message m, State state, boolean shared,
|
||||
boolean deleted) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||
+ " state, shared, length, raw)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getId().getBytes());
|
||||
ps.setBytes(2, m.getGroupId().getBytes());
|
||||
ps.setLong(3, m.getTimestamp());
|
||||
ps.setInt(4, state.getValue());
|
||||
ps.setBoolean(5, shared);
|
||||
byte[] raw = m.getRaw();
|
||||
ps.setInt(6, raw.length);
|
||||
if (deleted) ps.setNull(7, BINARY);
|
||||
else ps.setBytes(7, raw);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addStatus31(MessageId m, ContactId c) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, ack,"
|
||||
+ " seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, FALSE, FALSE, FALSE, 0, 0)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsStatus(MessageId m, ContactId c)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT COUNT (*) FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
int count = rs.getInt(1);
|
||||
if (count < 0 || count > 1) throw new DbStateException();
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return count > 0;
|
||||
} catch (SQLException e) {
|
||||
if (rs != null) rs.close();
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private Status32 getStatus32(MessageId m, ContactId c) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT groupId, timestamp, length, state,"
|
||||
+ " groupShared, messageShared, deleted"
|
||||
+ " FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId groupId = new GroupId(rs.getBytes(1));
|
||||
long timestamp = rs.getLong(2);
|
||||
int length = rs.getInt(3);
|
||||
State state = State.fromValue(rs.getInt(4));
|
||||
boolean groupShared = rs.getBoolean(5);
|
||||
boolean messageShared = rs.getBoolean(6);
|
||||
boolean deleted = rs.getBoolean(7);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return new Status32(groupId, timestamp, length, state,
|
||||
groupShared, messageShared, deleted);
|
||||
} catch (SQLException e) {
|
||||
if (rs != null) rs.close();
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Status32 {
|
||||
|
||||
private final GroupId groupId;
|
||||
private final long timestamp;
|
||||
private final int length;
|
||||
private final State state;
|
||||
private final boolean groupShared, messageShared, deleted;
|
||||
|
||||
private Status32(GroupId groupId, long timestamp, int length,
|
||||
State state, boolean groupShared, boolean messageShared,
|
||||
boolean deleted) {
|
||||
this.groupId = groupId;
|
||||
this.timestamp = timestamp;
|
||||
this.length = length;
|
||||
this.state = state;
|
||||
this.groupShared = groupShared;
|
||||
this.messageShared = messageShared;
|
||||
this.deleted = deleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,14 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.RunAction;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -29,37 +29,30 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
public class PollerTest extends BrambleMockTestCase {
|
||||
public class PollerTest extends BrambleTestCase {
|
||||
|
||||
private final ScheduledExecutorService scheduler =
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
private final ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
private final ConnectionRegistry connectionRegistry =
|
||||
context.mock(ConnectionRegistry.class);
|
||||
private final PluginManager pluginManager =
|
||||
context.mock(PluginManager.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
private final ScheduledFuture future = context.mock(ScheduledFuture.class);
|
||||
private final SecureRandom random;
|
||||
|
||||
private final Executor ioExecutor = new ImmediateExecutor();
|
||||
private final TransportId transportId = new TransportId("id");
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
private final int pollingInterval = 60 * 1000;
|
||||
private final long now = System.currentTimeMillis();
|
||||
|
||||
public PollerTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
random = context.mock(SecureRandom.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectOnContactStatusChanged() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
Executor ioExecutor = new ImmediateExecutor();
|
||||
ScheduledExecutorService scheduler =
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
ConnectionRegistry connectionRegistry =
|
||||
context.mock(ConnectionRegistry.class);
|
||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
||||
SecureRandom random = context.mock(SecureRandom.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
|
||||
// Two simplex plugins: one supports polling, the other doesn't
|
||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||
SimplexPlugin simplexPlugin1 =
|
||||
@@ -127,12 +120,28 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
|
||||
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRescheduleAndReconnectOnConnectionClosed()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
Executor ioExecutor = new ImmediateExecutor();
|
||||
ScheduledExecutorService scheduler =
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
ConnectionRegistry connectionRegistry =
|
||||
context.mock(ConnectionRegistry.class);
|
||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
||||
SecureRandom random = context.mock(SecureRandom.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
|
||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
||||
TransportId transportId = new TransportId("id");
|
||||
DuplexTransportConnection duplexConnection =
|
||||
context.mock(DuplexTransportConnection.class);
|
||||
|
||||
@@ -159,7 +168,6 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) pollingInterval), with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
// connectToContact()
|
||||
// Check whether the contact is already connected
|
||||
oneOf(connectionRegistry).isConnected(contactId, transportId);
|
||||
@@ -177,12 +185,28 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
|
||||
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
|
||||
false));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRescheduleOnConnectionOpened() throws Exception {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
Mockery context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
Executor ioExecutor = new ImmediateExecutor();
|
||||
ScheduledExecutorService scheduler =
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
ConnectionRegistry connectionRegistry =
|
||||
context.mock(ConnectionRegistry.class);
|
||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
||||
SecureRandom random = context.mock(SecureRandom.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
|
||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
||||
TransportId transportId = new TransportId("id");
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(plugin).getId();
|
||||
@@ -200,7 +224,6 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) pollingInterval), with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
@@ -208,11 +231,27 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
|
||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||
false));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
Mockery context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
Executor ioExecutor = new ImmediateExecutor();
|
||||
ScheduledExecutorService scheduler =
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
ConnectionRegistry connectionRegistry =
|
||||
context.mock(ConnectionRegistry.class);
|
||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
||||
SecureRandom random = context.mock(SecureRandom.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
|
||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
||||
TransportId transportId = new TransportId("id");
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(plugin).getId();
|
||||
@@ -231,7 +270,6 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) pollingInterval), with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
// Second event
|
||||
// Get the plugin
|
||||
oneOf(pluginManager).getPlugin(transportId);
|
||||
@@ -253,59 +291,27 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
false));
|
||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||
false));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRescheduleReplacesLaterTask() throws Exception {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(plugin).getId();
|
||||
will(returnValue(transportId));
|
||||
// First event
|
||||
// Get the plugin
|
||||
oneOf(pluginManager).getPlugin(transportId);
|
||||
will(returnValue(plugin));
|
||||
// The plugin supports polling
|
||||
oneOf(plugin).shouldPoll();
|
||||
will(returnValue(true));
|
||||
// Schedule the next poll
|
||||
oneOf(plugin).getPollingInterval();
|
||||
will(returnValue(pollingInterval));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) pollingInterval), with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
// Second event
|
||||
// Get the plugin
|
||||
oneOf(pluginManager).getPlugin(transportId);
|
||||
will(returnValue(plugin));
|
||||
// The plugin supports polling
|
||||
oneOf(plugin).shouldPoll();
|
||||
will(returnValue(true));
|
||||
// Replace the previously scheduled task, due later
|
||||
oneOf(plugin).getPollingInterval();
|
||||
will(returnValue(pollingInterval - 2));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now + 1));
|
||||
oneOf(future).cancel(false);
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) pollingInterval - 2), with(MILLISECONDS));
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
|
||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||
false));
|
||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||
false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPollsOnTransportEnabled() throws Exception {
|
||||
public void testPollOnTransportEnabled() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
Executor ioExecutor = new ImmediateExecutor();
|
||||
ScheduledExecutorService scheduler =
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
ConnectionManager connectionManager =
|
||||
context.mock(ConnectionManager.class);
|
||||
ConnectionRegistry connectionRegistry =
|
||||
context.mock(ConnectionRegistry.class);
|
||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
||||
SecureRandom random = context.mock(SecureRandom.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
TransportId transportId = new TransportId("id");
|
||||
List<ContactId> connected = Collections.singletonList(contactId);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
@@ -322,7 +328,6 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
||||
with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
will(new RunAction());
|
||||
// Running the polling task schedules the next polling task
|
||||
oneOf(plugin).getPollingInterval();
|
||||
@@ -333,7 +338,6 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
// Poll the plugin
|
||||
oneOf(connectionRegistry).getConnectedContacts(transportId);
|
||||
will(returnValue(connected));
|
||||
@@ -344,36 +348,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
|
||||
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelsPollingOnTransportDisabled() throws Exception {
|
||||
Plugin plugin = context.mock(Plugin.class);
|
||||
List<ContactId> connected = Collections.singletonList(contactId);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(plugin).getId();
|
||||
will(returnValue(transportId));
|
||||
// Get the plugin
|
||||
oneOf(pluginManager).getPlugin(transportId);
|
||||
will(returnValue(plugin));
|
||||
// The plugin supports polling
|
||||
oneOf(plugin).shouldPoll();
|
||||
will(returnValue(true));
|
||||
// Schedule a polling task immediately
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(now));
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
||||
with(MILLISECONDS));
|
||||
will(returnValue(future));
|
||||
// The plugin is disabled before the task runs - cancel the task
|
||||
oneOf(future).cancel(false);
|
||||
}});
|
||||
|
||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||
connectionRegistry, pluginManager, random, clock);
|
||||
|
||||
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||
p.eventOccurred(new TransportDisabledEvent(transportId));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -10,7 +11,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.plugin.tcp.LanTcpPlugin.LanAddressComparator;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -22,12 +22,13 @@ import java.net.NetworkInterface;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
@@ -55,9 +56,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
// Local and remote in 192.168.0.0/16 should return true
|
||||
assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
|
||||
makeAddress(192, 168, 255, 255)));
|
||||
// Local and remote in 169.254.0.0/16 (link-local) should return true
|
||||
assertTrue(plugin.addressesAreOnSameLan(makeAddress(169, 254, 0, 0),
|
||||
makeAddress(169, 254, 255, 255)));
|
||||
// Local and remote in different recognised prefixes should return false
|
||||
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
|
||||
makeAddress(172, 31, 255, 255)));
|
||||
@@ -152,17 +150,14 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
int port = ss.getLocalPort();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean error = new AtomicBoolean(false);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ss.accept();
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
error.set(true);
|
||||
}
|
||||
new Thread(() -> {
|
||||
try {
|
||||
ss.accept();
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
error.set(true);
|
||||
}
|
||||
}.start();
|
||||
}).start();
|
||||
// Tell the plugin about the port
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put("ipPorts", addrString + ":" + port);
|
||||
@@ -195,16 +190,9 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
KeyAgreementListener kal =
|
||||
plugin.createKeyAgreementListener(new byte[COMMIT_LENGTH]);
|
||||
assertNotNull(kal);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean error = new AtomicBoolean(false);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
kal.accept();
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
error.set(true);
|
||||
}
|
||||
}).start();
|
||||
Callable<KeyAgreementConnection> c = kal.listen();
|
||||
FutureTask<KeyAgreementConnection> f = new FutureTask<>(c);
|
||||
new Thread(f).start();
|
||||
// The plugin should have bound a socket and stored the port number
|
||||
BdfList descriptor = kal.getDescriptor();
|
||||
assertEquals(3, descriptor.size());
|
||||
@@ -220,12 +208,10 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
InetSocketAddress socketAddr = new InetSocketAddress(addr, port);
|
||||
Socket s = new Socket();
|
||||
s.connect(socketAddr, 100);
|
||||
// Check that the connection was accepted
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
assertFalse(error.get());
|
||||
// Clean up
|
||||
assertNotNull(f.get(5, SECONDS));
|
||||
s.close();
|
||||
kal.close();
|
||||
// Stop the plugin
|
||||
plugin.stop();
|
||||
}
|
||||
|
||||
@@ -254,17 +240,14 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
ss.bind(new InetSocketAddress(addrString, 0), 10);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean error = new AtomicBoolean(false);
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ss.accept();
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
error.set(true);
|
||||
}
|
||||
new Thread(() -> {
|
||||
try {
|
||||
ss.accept();
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
error.set(true);
|
||||
}
|
||||
}.start();
|
||||
}).start();
|
||||
// Tell the plugin about the port
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_LAN);
|
||||
@@ -274,7 +257,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
descriptor.add(local.getPort());
|
||||
// Connect to the port
|
||||
DuplexTransportConnection d = plugin.createKeyAgreementConnection(
|
||||
new byte[COMMIT_LENGTH], descriptor);
|
||||
new byte[COMMIT_LENGTH], descriptor, 5000);
|
||||
assertNotNull(d);
|
||||
// Check that the connection was accepted
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
@@ -286,57 +269,6 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
plugin.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorPrefersNonZeroPorts() throws Exception {
|
||||
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||
InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234);
|
||||
InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0);
|
||||
|
||||
assertEquals(0, comparator.compare(nonZero, nonZero));
|
||||
assertTrue(comparator.compare(nonZero, zero) < 0);
|
||||
|
||||
assertTrue(comparator.compare(zero, nonZero) > 0);
|
||||
assertEquals(0, comparator.compare(zero, zero));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorPrefersLongerPrefixes() throws Exception {
|
||||
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
|
||||
|
||||
assertEquals(0, comparator.compare(prefix192, prefix192));
|
||||
assertTrue(comparator.compare(prefix192, prefix172) < 0);
|
||||
assertTrue(comparator.compare(prefix192, prefix10) < 0);
|
||||
|
||||
assertTrue(comparator.compare(prefix172, prefix192) > 0);
|
||||
assertEquals(0, comparator.compare(prefix172, prefix172));
|
||||
assertTrue(comparator.compare(prefix172, prefix10) < 0);
|
||||
|
||||
assertTrue(comparator.compare(prefix10, prefix192) > 0);
|
||||
assertTrue(comparator.compare(prefix10, prefix172) > 0);
|
||||
assertEquals(0, comparator.compare(prefix10, prefix10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparatorPrefersSiteLocalToLinkLocal() throws Exception {
|
||||
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
|
||||
InetSocketAddress linkLocal = new InetSocketAddress("169.254.0.1", 0);
|
||||
|
||||
assertTrue(comparator.compare(prefix192, linkLocal) < 0);
|
||||
assertTrue(comparator.compare(prefix172, linkLocal) < 0);
|
||||
assertTrue(comparator.compare(prefix10, linkLocal) < 0);
|
||||
|
||||
assertTrue(comparator.compare(linkLocal, prefix192) > 0);
|
||||
assertTrue(comparator.compare(linkLocal, prefix172) > 0);
|
||||
assertTrue(comparator.compare(linkLocal, prefix10) > 0);
|
||||
assertEquals(0, comparator.compare(linkLocal, linkLocal));
|
||||
}
|
||||
|
||||
private boolean systemHasLocalIpv4Address() throws Exception {
|
||||
for (NetworkInterface i : Collections.list(
|
||||
NetworkInterface.getNetworkInterfaces())) {
|
||||
|
||||
@@ -94,8 +94,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
@@ -125,21 +123,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
will(returnValue(true));
|
||||
}});
|
||||
|
||||
TransportPropertyManagerImpl t = createInstance();
|
||||
t.createLocalState(txn);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
||||
public void testCreatesGroupWhenAddingContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup();
|
||||
@@ -231,6 +215,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
long timestamp = 123456789;
|
||||
Message message = getMessage(contactGroupId, timestamp);
|
||||
Metadata meta = new Metadata();
|
||||
// Version 4 is being delivered
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 4),
|
||||
@@ -238,19 +223,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
// Old remote updates for the same transport should be deleted
|
||||
MessageId fooVersion2 = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooVersion2, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 2),
|
||||
new BdfEntry("local", false)
|
||||
));
|
||||
MessageId fooVersion1 = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooVersion1, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 1),
|
||||
new BdfEntry("local", false)
|
||||
));
|
||||
// An older remote update for the same transport should be deleted
|
||||
MessageId fooVersion3 = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooVersion3, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
@@ -264,11 +237,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
// Versions 1-3 should be deleted
|
||||
oneOf(db).deleteMessage(txn, fooVersion1);
|
||||
oneOf(db).deleteMessageMetadata(txn, fooVersion1);
|
||||
oneOf(db).deleteMessage(txn, fooVersion2);
|
||||
oneOf(db).deleteMessageMetadata(txn, fooVersion2);
|
||||
// The previous update (version 3) should be deleted
|
||||
oneOf(db).deleteMessage(txn, fooVersion3);
|
||||
oneOf(db).deleteMessageMetadata(txn, fooVersion3);
|
||||
}});
|
||||
@@ -284,6 +253,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
long timestamp = 123456789;
|
||||
Message message = getMessage(contactGroupId, timestamp);
|
||||
Metadata meta = new Metadata();
|
||||
// Version 3 is being delivered
|
||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 3),
|
||||
@@ -291,19 +261,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
);
|
||||
Map<MessageId, BdfDictionary> messageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
// Old remote updates for the same transport should be deleted
|
||||
MessageId fooVersion2 = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooVersion2, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 2),
|
||||
new BdfEntry("local", false)
|
||||
));
|
||||
MessageId fooVersion1 = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooVersion1, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 1),
|
||||
new BdfEntry("local", false)
|
||||
));
|
||||
// A newer remote update for the same transport should not be deleted
|
||||
MessageId fooVersion4 = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooVersion4, BdfDictionary.of(
|
||||
@@ -318,11 +275,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId);
|
||||
will(returnValue(messageMetadata));
|
||||
// Versions 1 and 2 should be deleted, version 4 should not
|
||||
oneOf(db).deleteMessage(txn, fooVersion1);
|
||||
oneOf(db).deleteMessageMetadata(txn, fooVersion1);
|
||||
oneOf(db).deleteMessage(txn, fooVersion2);
|
||||
oneOf(db).deleteMessageMetadata(txn, fooVersion2);
|
||||
// The update being delivered (version 3) should be deleted
|
||||
oneOf(db).deleteMessage(txn, message.getId());
|
||||
oneOf(db).deleteMessageMetadata(txn, message.getId());
|
||||
@@ -359,7 +311,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testReturnsLatestLocalProperties() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
expectGetLocalProperties(txn);
|
||||
|
||||
@@ -373,7 +325,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testReturnsEmptyPropertiesIfNoLocalPropertiesAreFound()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Map<MessageId, BdfDictionary> messageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
// A local update for another transport should be ignored
|
||||
@@ -385,7 +337,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
localGroup.getId());
|
||||
@@ -400,7 +352,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testReturnsLocalProperties() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Map<MessageId, BdfDictionary> messageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
// A local update for another transport should be ignored
|
||||
@@ -420,7 +372,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
localGroup.getId());
|
||||
@@ -439,7 +391,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testReturnsRemotePropertiesOrEmptyProperties()
|
||||
throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Transaction txn = new Transaction(null, true);
|
||||
Contact contact1 = getContact(false);
|
||||
Contact contact2 = getContact(true);
|
||||
Contact contact3 = getContact(true);
|
||||
@@ -473,7 +425,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
@@ -654,28 +606,14 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
return new Message(messageId, g, timestamp, raw);
|
||||
}
|
||||
|
||||
private void expectGetLocalProperties(Transaction txn)
|
||||
throws Exception {
|
||||
Map<MessageId, BdfDictionary> messageMetadata =
|
||||
new LinkedHashMap<>();
|
||||
// The only update for transport "foo" should be returned
|
||||
private void expectGetLocalProperties(Transaction txn) throws Exception {
|
||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
||||
// The latest update for transport "foo" should be returned
|
||||
MessageId fooVersion999 = new MessageId(getRandomId());
|
||||
messageMetadata.put(fooVersion999, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 999)
|
||||
));
|
||||
// An old update for transport "bar" should be deleted
|
||||
MessageId barVersion2 = new MessageId(getRandomId());
|
||||
messageMetadata.put(barVersion2, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "bar"),
|
||||
new BdfEntry("version", 2)
|
||||
));
|
||||
// An even older update for transport "bar" should be deleted
|
||||
MessageId barVersion1 = new MessageId(getRandomId());
|
||||
messageMetadata.put(barVersion1, BdfDictionary.of(
|
||||
new BdfEntry("transportId", "bar"),
|
||||
new BdfEntry("version", 1)
|
||||
));
|
||||
// The latest update for transport "bar" should be returned
|
||||
MessageId barVersion3 = new MessageId(getRandomId());
|
||||
messageMetadata.put(barVersion3, BdfDictionary.of(
|
||||
@@ -690,8 +628,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
localGroup.getId());
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(db).removeMessage(txn, barVersion1);
|
||||
oneOf(db).removeMessage(txn, barVersion2);
|
||||
// Retrieve and parse the latest local properties
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
|
||||
will(returnValue(fooUpdate));
|
||||
|
||||
@@ -100,21 +100,21 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// validateOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// deliverOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).getPendingMessages(txn1);
|
||||
oneOf(db).getPendingMessages(txn1, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
// shareOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn2));
|
||||
oneOf(db).getMessagesToShare(txn2);
|
||||
oneOf(db).getMessagesToShare(txn2, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn2);
|
||||
oneOf(db).endTransaction(txn2);
|
||||
@@ -138,7 +138,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
will(returnValue(Arrays.asList(messageId, messageId1)));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
@@ -199,14 +199,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn5));
|
||||
oneOf(db).getPendingMessages(txn5);
|
||||
oneOf(db).getPendingMessages(txn5, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn5);
|
||||
oneOf(db).endTransaction(txn5);
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn6));
|
||||
oneOf(db).getMessagesToShare(txn6);
|
||||
oneOf(db).getMessagesToShare(txn6, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn6);
|
||||
oneOf(db).endTransaction(txn6);
|
||||
@@ -227,14 +227,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).getPendingMessages(txn1);
|
||||
oneOf(db).getPendingMessages(txn1, clientId);
|
||||
will(returnValue(Collections.singletonList(messageId)));
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
@@ -292,7 +292,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn4));
|
||||
oneOf(db).getMessagesToShare(txn4);
|
||||
oneOf(db).getMessagesToShare(txn4, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn4);
|
||||
oneOf(db).endTransaction(txn4);
|
||||
@@ -313,14 +313,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// No messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// No pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).getPendingMessages(txn1);
|
||||
oneOf(db).getPendingMessages(txn1, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
@@ -328,7 +328,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn2));
|
||||
oneOf(db).getMessagesToShare(txn2);
|
||||
oneOf(db).getMessagesToShare(txn2, clientId);
|
||||
will(returnValue(Collections.singletonList(messageId)));
|
||||
oneOf(db).commitTransaction(txn2);
|
||||
oneOf(db).endTransaction(txn2);
|
||||
@@ -416,7 +416,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
will(returnValue(Arrays.asList(messageId, messageId1)));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
@@ -457,14 +457,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn4));
|
||||
oneOf(db).getPendingMessages(txn4);
|
||||
oneOf(db).getPendingMessages(txn4, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn4);
|
||||
oneOf(db).endTransaction(txn4);
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn5));
|
||||
oneOf(db).getMessagesToShare(txn5);
|
||||
oneOf(db).getMessagesToShare(txn5, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn5);
|
||||
oneOf(db).endTransaction(txn5);
|
||||
@@ -487,7 +487,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
will(returnValue(Arrays.asList(messageId, messageId1)));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
@@ -533,14 +533,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn4));
|
||||
oneOf(db).getPendingMessages(txn4);
|
||||
oneOf(db).getPendingMessages(txn4, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn4);
|
||||
oneOf(db).endTransaction(txn4);
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn5));
|
||||
oneOf(db).getMessagesToShare(txn5);
|
||||
oneOf(db).getMessagesToShare(txn5, clientId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn5);
|
||||
oneOf(db).endTransaction(txn5);
|
||||
|
||||
@@ -17,8 +17,6 @@ import javax.inject.Singleton;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
|
||||
@Module
|
||||
public class TestLifecycleModule {
|
||||
|
||||
@@ -59,11 +57,6 @@ public class TestLifecycleModule {
|
||||
@Override
|
||||
public void waitForShutdown() throws InterruptedException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return RUNNING;
|
||||
}
|
||||
};
|
||||
return lifecycleManager;
|
||||
}
|
||||
|
||||
@@ -12,16 +12,16 @@ dependencies {
|
||||
implementation 'net.java.dev.jna:jna:4.4.0'
|
||||
implementation 'net.java.dev.jna:jna-platform:4.4.0'
|
||||
|
||||
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
apt "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||
testImplementation project(path: ':bramble-core', 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"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
testImplementation "org.jmock:jmock:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
@@ -36,6 +36,7 @@ dependencyVerification {
|
||||
'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',
|
||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||
'net.java.dev.jna:jna-platform:4.4.0:jna-platform-4.4.0.jar:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
|
||||
'net.java.dev.jna:jna:4.4.0:jna-4.4.0.jar:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
|
||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -9,7 +8,7 @@ import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.BluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory;
|
||||
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||
@@ -31,10 +30,9 @@ public class DesktopPluginModule extends PluginModule {
|
||||
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
|
||||
SecureRandom random, BackoffFactory backoffFactory,
|
||||
ReliabilityLayerFactory reliabilityFactory,
|
||||
ShutdownManager shutdownManager, EventBus eventBus) {
|
||||
DuplexPluginFactory bluetooth =
|
||||
new JavaBluetoothPluginFactory(ioExecutor, random, eventBus,
|
||||
backoffFactory);
|
||||
ShutdownManager shutdownManager) {
|
||||
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(ioExecutor,
|
||||
random, backoffFactory);
|
||||
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
|
||||
reliabilityFactory);
|
||||
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
|
||||
|
||||
@@ -3,8 +3,6 @@ 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;
|
||||
@@ -15,11 +13,8 @@ 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.OsUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -28,25 +23,30 @@ 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.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.LocalDevice;
|
||||
import javax.microedition.io.Connector;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
||||
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 {
|
||||
class BluetoothPlugin implements DuplexPlugin {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
@@ -58,38 +58,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
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;
|
||||
private volatile boolean running = false;
|
||||
private volatile StreamConnectionNotifier socket = null;
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
@@ -100,19 +71,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
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;
|
||||
@@ -132,103 +90,107 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
// Initialise the Bluetooth stack
|
||||
try {
|
||||
initialiseAdapter();
|
||||
} catch (IOException e) {
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
// On Linux the user may need to install libbluetooth-dev
|
||||
if (OsUtils.isLinux())
|
||||
callback.showMessage("BLUETOOTH_INSTALL_LIBS");
|
||||
throw new PluginException(e);
|
||||
} catch (BluetoothStateException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
updateProperties();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + localDevice.getBluetoothAddress());
|
||||
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;
|
||||
bind();
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
if (!running) return;
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ADDRESS, localDevice.getBluetoothAddress());
|
||||
callback.mergeLocalProperties(p);
|
||||
// Bind a server socket to accept connections from contacts
|
||||
SS ss;
|
||||
String url = makeUrl("localhost", getUuid());
|
||||
StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = openServerSocket(contactConnectionsUuid);
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!isRunning() || !shouldAllowContactConnections()) {
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
acceptContactConnections(ss);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
private String 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);
|
||||
changed = true;
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
contactConnectionsUuid = uuid;
|
||||
if (changed) callback.mergeLocalProperties(p);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private void acceptContactConnections() {
|
||||
private void tryToClose(@Nullable StreamConnectionNotifier 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(StreamConnectionNotifier ss) {
|
||||
while (true) {
|
||||
DuplexTransportConnection conn;
|
||||
StreamConnection s;
|
||||
try {
|
||||
conn = acceptConnection(socket);
|
||||
s = ss.acceptAndOpen();
|
||||
} 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);
|
||||
callback.incomingConnectionCreated(wrapSocket(s));
|
||||
if (!running) return;
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
tryToClose(socket);
|
||||
callback.transportDisabled();
|
||||
disableAdapterIfEnabledByUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running && isAdapterEnabled();
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -243,7 +205,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
if (!running) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
@@ -256,56 +218,41 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
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) {
|
||||
if (!running) return;
|
||||
StreamConnection s = connect(makeUrl(address, uuid));
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
private StreamConnection connect(String url) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + url);
|
||||
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;
|
||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Connected to " + url);
|
||||
return s;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Could not connect to " + scrubMacAddress(address));
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + url);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||
if (!running) 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);
|
||||
String url = makeUrl(address, uuid);
|
||||
StreamConnection s = connect(url);
|
||||
if (s == null) return null;
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -315,34 +262,35 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
|
||||
@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;
|
||||
if (!running) 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;
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
// Bind a server socket for receiving key agreementconnections
|
||||
StreamConnectionNotifier ss;
|
||||
try {
|
||||
ss = openServerSocket(uuid);
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
if (!running) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
String address = localDevice.getBluetoothAddress();
|
||||
descriptor.add(StringUtils.macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
if (!isRunning()) return null;
|
||||
String address;
|
||||
try {
|
||||
@@ -355,7 +303,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
return connect(address, uuid);
|
||||
String url = makeUrl(address, uuid);
|
||||
StreamConnection s = connect(url);
|
||||
if (s == null) return null;
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
@@ -364,56 +315,44 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
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 void makeDeviceDiscoverable() {
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
try {
|
||||
localDevice.setDiscoverable(GIAC);
|
||||
} catch (BluetoothStateException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final SS ss;
|
||||
private final StreamConnectionNotifier ss;
|
||||
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor,
|
||||
StreamConnectionNotifier 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);
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return () -> {
|
||||
StreamConnection s = ss.acceptAndOpen();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new BluetoothTransportConnection(
|
||||
BluetoothPlugin.this, s), ID);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
tryToClose(ss);
|
||||
try {
|
||||
ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
@@ -18,7 +17,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public class BluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||
@@ -28,15 +27,12 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
private final Executor ioExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final BackoffFactory backoffFactory;
|
||||
private final EventBus eventBus;
|
||||
|
||||
public JavaBluetoothPluginFactory(Executor ioExecutor,
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
public BluetoothPluginFactory(Executor ioExecutor,
|
||||
SecureRandom secureRandom, BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoffFactory = backoffFactory;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,9 +49,7 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
return new BluetoothPlugin(ioExecutor, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,11 @@ import java.io.OutputStream;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
|
||||
@NotNullByDefault
|
||||
class JavaBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
class BluetoothTransportConnection extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final StreamConnection stream;
|
||||
|
||||
JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||
BluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||
super(plugin);
|
||||
this.stream = stream;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
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.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.LocalDevice;
|
||||
import javax.microedition.io.Connector;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.StringUtils.isValidMac;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JavaBluetoothPlugin.class.getName());
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialiseAdapter() throws IOException {
|
||||
try {
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
} catch (UnsatisfiedLinkError | BluetoothStateException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAdapterEnabled() {
|
||||
return localDevice != null && LocalDevice.isPowerOn();
|
||||
}
|
||||
|
||||
@Override
|
||||
void enableAdapter() {
|
||||
// Nothing we can do on this platform
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
|
||||
@Override
|
||||
void disableAdapterIfEnabledByUs() {
|
||||
// We didn't enable it so we don't need to disable it
|
||||
}
|
||||
|
||||
@Override
|
||||
void setEnabledByUs() {
|
||||
// Irrelevant on this platform
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getBluetoothAddress() {
|
||||
return localDevice.getBluetoothAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
StreamConnectionNotifier openServerSocket(String uuid) throws IOException {
|
||||
String url = makeUrl("localhost", uuid);
|
||||
return (StreamConnectionNotifier) Connector.open(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
void tryToClose(@Nullable StreamConnectionNotifier ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection acceptConnection(StreamConnectionNotifier ss)
|
||||
throws IOException {
|
||||
return wrapSocket(ss.acceptAndOpen());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isValidAddress(String address) {
|
||||
return isValidMac(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException {
|
||||
String url = makeUrl(address, uuid);
|
||||
return wrapSocket((StreamConnection) Connector.open(url));
|
||||
}
|
||||
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new JavaBluetoothTransportConnection(this, s);
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN
|
||||
lang_map = pt_BR: pt-rBR, fr_FR: fr, nb_NO: nb, zh-Hans: zh-rCN
|
||||
|
||||
[briar.stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.preferProjectModules()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':briar-core', configuration: 'default')
|
||||
implementation project(path: ':bramble-core', configuration: 'default')
|
||||
implementation project(path: ':bramble-android', configuration: 'default')
|
||||
|
||||
def supportVersion = '27.0.1'
|
||||
implementation "com.android.support:support-v4:$supportVersion"
|
||||
implementation("com.android.support:appcompat-v7:$supportVersion") {
|
||||
exclude module: 'support-v4'
|
||||
@@ -24,7 +19,7 @@ dependencies {
|
||||
}
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation "com.android.support:support-annotations:$supportVersion"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta3'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
|
||||
implementation('ch.acra:acra:4.8.5') {
|
||||
exclude module: 'support-v4'
|
||||
@@ -38,7 +33,7 @@ dependencies {
|
||||
implementation 'com.github.bumptech.glide:glide:3.8.0'
|
||||
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.1.0'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
compileOnly 'javax.annotation:jsr250-api:1.0'
|
||||
|
||||
@@ -47,12 +42,12 @@ dependencies {
|
||||
testImplementation 'org.robolectric:robolectric:3.5.1'
|
||||
testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||
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"
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
testImplementation "org.jmock:jmock:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
|
||||
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
@@ -65,8 +60,8 @@ dependencyVerification {
|
||||
'ch.acra:acra:4.8.5:acra-4.8.5.aar:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb',
|
||||
'classworlds:classworlds:1.1-alpha-2:classworlds-1.1-alpha-2.jar:2bf4e59f3acd106fea6145a9a88fe8956509f8b9c0fdd11eb96fee757269e3f3',
|
||||
'com.almworks.sqlite4java:sqlite4java:0.282:sqlite4java-0.282.jar:9e1d8dd83ca6003f841e3af878ce2dc7c22497493a7bb6d1b62ec1b0d0a83c05',
|
||||
'com.android.support.constraint:constraint-layout-solver:1.1.0-beta3:constraint-layout-solver-1.1.0-beta3.jar:c9084108415046c423983bdff8cf04c8e9a5bed41b8d5329f3764c08312ee3dd',
|
||||
'com.android.support.constraint:constraint-layout:1.1.0-beta3:constraint-layout-1.1.0-beta3.aar:1754a6bd135feae485aa2ebf9e170f0f3d3282b392f8aa3067d0ed668839db79',
|
||||
'com.android.support.constraint:constraint-layout-solver:1.0.2:constraint-layout-solver-1.0.2.jar:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
|
||||
'com.android.support.constraint:constraint-layout:1.0.2:constraint-layout-1.0.2.aar:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
|
||||
'com.android.support:animated-vector-drawable:27.0.1:animated-vector-drawable-27.0.1.aar:365050110411c86c7eec86101b49ab53557ffe6667f60b19055f1d35c38a577b',
|
||||
'com.android.support:appcompat-v7:27.0.1:appcompat-v7-27.0.1.aar:1402c29a49db30346c21a7d40634461765b3ab826f5dd95bc4dcc76787b21851',
|
||||
'com.android.support:cardview-v7:27.0.1:cardview-v7-27.0.1.aar:43fccd44086c51eaa9d78be2fcf0dfea1556c8876a6fd325ea8d24e860054202',
|
||||
@@ -112,6 +107,7 @@ dependencyVerification {
|
||||
'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438',
|
||||
'net.bytebuddy:byte-buddy-agent:1.6.14:byte-buddy-agent-1.6.14.jar:c141a2d6809c3eeff4a43d25992826abccebdd4b793af3e7a5f346e88ae73a33',
|
||||
'net.bytebuddy:byte-buddy:1.6.14:byte-buddy-1.6.14.jar:917758b3c651e278a15a029ba1d42dbf802d8b0e1fe2aa4b81c5750c64f461c1',
|
||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||
'org.apache.maven.wagon:wagon-file:1.0-beta-6:wagon-file-1.0-beta-6.jar:7298feeb36ff14dd933c38e62585fb9973fea32fb3c4bc5379428cb1aac5dd3c',
|
||||
@@ -189,19 +185,19 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 1621
|
||||
versionName "0.16.21"
|
||||
applicationId "org.briarproject.briar.beta"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||
resValue "string", "app_name", "Briar Beta"
|
||||
versionCode 1700
|
||||
versionName "0.17.0"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
resValue "string", "app_package", "org.briarproject.briar.android"
|
||||
resValue "string", "app_name", "Briar"
|
||||
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta.debug"
|
||||
resValue "string", "app_name", "Briar Beta Debug"
|
||||
resValue "string", "app_package", "org.briarproject.briar.android.debug"
|
||||
resValue "string", "app_name", "Briar Debug"
|
||||
shrinkResources false
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
|
||||
@@ -77,11 +77,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.login.OpenDatabaseActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"/>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
@@ -374,12 +369,7 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.logout.ExitActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.logout.HideUiActivity"
|
||||
android:name="org.briarproject.briar.android.panic.ExitActivity"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.briarproject.bramble.BrambleAndroidModule;
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
@@ -91,8 +89,6 @@ public interface AndroidComponent
|
||||
|
||||
AndroidNotificationManager androidNotificationManager();
|
||||
|
||||
SharedPreferences sharedPreferences();
|
||||
|
||||
ScreenFilterMonitor screenFilterMonitor();
|
||||
|
||||
ConnectionRegistry connectionRegistry();
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -48,7 +44,6 @@ import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
@@ -62,11 +57,8 @@ import javax.inject.Inject;
|
||||
import static android.app.Notification.DEFAULT_LIGHTS;
|
||||
import static android.app.Notification.DEFAULT_SOUND;
|
||||
import static android.app.Notification.DEFAULT_VIBRATE;
|
||||
import static android.app.Notification.VISIBILITY_SECRET;
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
|
||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -99,9 +91,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private final Executor dbExecutor;
|
||||
private final SettingsManager settingsManager;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Clock clock;
|
||||
private final Context appContext;
|
||||
private final NotificationManager notificationManager;
|
||||
private final Clock clock;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
// The following must only be accessed on the main UI thread
|
||||
@@ -130,8 +121,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.clock = clock;
|
||||
appContext = app.getApplicationContext();
|
||||
notificationManager = (NotificationManager)
|
||||
appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,39 +132,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
} catch (DbException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
if (SDK_INT >= 26) {
|
||||
// Create notification channels
|
||||
Callable<Void> task = () -> {
|
||||
createNotificationChannel(CONTACT_CHANNEL_ID,
|
||||
R.string.contact_list_button);
|
||||
createNotificationChannel(GROUP_CHANNEL_ID,
|
||||
R.string.groups_button);
|
||||
createNotificationChannel(FORUM_CHANNEL_ID,
|
||||
R.string.forums_button);
|
||||
createNotificationChannel(BLOG_CHANNEL_ID,
|
||||
R.string.blogs_button);
|
||||
return null;
|
||||
};
|
||||
try {
|
||||
androidExecutor.runOnUiThread(task).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(26)
|
||||
private void createNotificationChannel(String channelId,
|
||||
@StringRes int name) {
|
||||
NotificationChannel nc =
|
||||
new NotificationChannel(channelId, appContext.getString(name),
|
||||
IMPORTANCE_DEFAULT);
|
||||
nc.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
nc.enableVibration(true);
|
||||
nc.enableLights(true);
|
||||
nc.setLightColor(
|
||||
ContextCompat.getColor(appContext, R.color.briar_green_light));
|
||||
notificationManager.createNotificationChannel(nc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,34 +156,44 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private void clearContactNotification() {
|
||||
contactCounts.clear();
|
||||
contactTotal = 0;
|
||||
notificationManager.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearGroupMessageNotification() {
|
||||
groupCounts.clear();
|
||||
groupTotal = 0;
|
||||
notificationManager.cancel(GROUP_MESSAGE_NOTIFICATION_ID);
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.cancel(GROUP_MESSAGE_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearForumPostNotification() {
|
||||
forumCounts.clear();
|
||||
forumTotal = 0;
|
||||
notificationManager.cancel(FORUM_POST_NOTIFICATION_ID);
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.cancel(FORUM_POST_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearBlogPostNotification() {
|
||||
blogCounts.clear();
|
||||
blogTotal = 0;
|
||||
notificationManager.cancel(BLOG_POST_NOTIFICATION_ID);
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.cancel(BLOG_POST_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearIntroductionSuccessNotification() {
|
||||
introductionTotal = 0;
|
||||
notificationManager.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID);
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -303,8 +269,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
if (contactTotal == 0) {
|
||||
clearContactNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
|
||||
BriarNotificationBuilder b = new BriarNotificationBuilder(
|
||||
appContext, CONTACT_CHANNEL_ID);
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_private_message);
|
||||
b.setColorRes(R.color.briar_primary);
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
@@ -339,8 +305,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
}
|
||||
notificationManager.notify(PRIVATE_MESSAGE_NOTIFICATION_ID,
|
||||
b.build());
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(PRIVATE_MESSAGE_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +378,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
clearGroupMessageNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) {
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext, GROUP_CHANNEL_ID);
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_private_group);
|
||||
b.setColorRes(R.color.briar_primary);
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
@@ -447,8 +414,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
}
|
||||
notificationManager.notify(GROUP_MESSAGE_NOTIFICATION_ID,
|
||||
b.build());
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(GROUP_MESSAGE_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +455,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
clearForumPostNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext, FORUM_CHANNEL_ID);
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_forum);
|
||||
b.setColorRes(R.color.briar_primary);
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
@@ -523,7 +491,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
}
|
||||
notificationManager.notify(FORUM_POST_NOTIFICATION_ID, b.build());
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(FORUM_POST_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +532,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
clearBlogPostNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext, BLOG_CHANNEL_ID);
|
||||
new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_blog);
|
||||
b.setColorRes(R.color.briar_primary);
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
@@ -585,7 +555,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
|
||||
notificationManager.notify(BLOG_POST_NOTIFICATION_ID, b.build());
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(BLOG_POST_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,8 +577,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
|
||||
@UiThread
|
||||
private void updateIntroductionNotification() {
|
||||
BriarNotificationBuilder b =
|
||||
new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID);
|
||||
BriarNotificationBuilder b = new BriarNotificationBuilder(appContext);
|
||||
b.setSmallIcon(R.drawable.notification_introduction);
|
||||
b.setColorRes(R.color.briar_primary);
|
||||
b.setContentTitle(appContext.getText(R.string.app_name));
|
||||
@@ -628,8 +599,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
|
||||
notificationManager.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID,
|
||||
b.build());
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
@@ -94,7 +93,6 @@ public class AppModule {
|
||||
|
||||
@Override
|
||||
public boolean databaseExists() {
|
||||
// FIXME should not run on UiThread #620
|
||||
if (!dir.isDirectory()) return false;
|
||||
File[] files = dir.listFiles();
|
||||
return files != null && files.length > 0;
|
||||
@@ -159,11 +157,6 @@ public class AppModule {
|
||||
return devConfig;
|
||||
}
|
||||
|
||||
@Provides
|
||||
SharedPreferences provideSharedPreferences(Application app) {
|
||||
return app.getSharedPreferences("db", MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ReferenceManager provideReferenceManager() {
|
||||
@@ -181,11 +174,8 @@ public class AppModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||
LifecycleManager lifecycleManager,
|
||||
ScreenFilterMonitorImpl screenFilterMonitor) {
|
||||
lifecycleManager.registerService(screenFilterMonitor);
|
||||
return screenFilterMonitor;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ package org.briarproject.briar.android;
|
||||
*/
|
||||
public interface BriarApplication {
|
||||
|
||||
// This build expires on 30 April 2018
|
||||
long EXPIRY_DATE = 1525046400 * 1000L;
|
||||
// This build expires on 31 December 2018
|
||||
long EXPIRY_DATE = 1546214400 * 1000L;
|
||||
|
||||
AndroidComponent getApplicationComponent();
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@ import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
@@ -20,27 +18,17 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.logout.HideUiActivity;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
import static android.content.Intent.ACTION_SHUTDOWN;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
||||
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||
@@ -50,30 +38,15 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
|
||||
|
||||
public class BriarService extends Service {
|
||||
|
||||
public static String EXTRA_START_RESULT =
|
||||
"org.briarproject.briar.START_RESULT";
|
||||
public static String EXTRA_NOTIFICATION_ID =
|
||||
"org.briarproject.briar.FAILURE_NOTIFICATION_ID";
|
||||
public static String EXTRA_STARTUP_FAILED =
|
||||
"org.briarproject.briar.STARTUP_FAILED";
|
||||
|
||||
private static final int ONGOING_NOTIFICATION_ID = 1;
|
||||
private static final int FAILURE_NOTIFICATION_ID = 2;
|
||||
|
||||
// Channels are sorted by channel ID in the Settings app, so use IDs
|
||||
// that will sort below the main channels such as contacts
|
||||
private static final String ONGOING_CHANNEL_ID = "zForegroundService";
|
||||
private static final String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BriarService.class.getName());
|
||||
|
||||
private final AtomicBoolean created = new AtomicBoolean(false);
|
||||
private final Binder binder = new BriarBinder();
|
||||
|
||||
@Nullable
|
||||
private BroadcastReceiver receiver = null;
|
||||
|
||||
@Inject
|
||||
protected DatabaseConfig databaseConfig;
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@@ -101,27 +74,20 @@ public class BriarService extends Service {
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create notification channels
|
||||
if (SDK_INT >= 26) {
|
||||
NotificationManager nm = (NotificationManager)
|
||||
getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationChannel ongoingChannel = new NotificationChannel(
|
||||
ONGOING_CHANNEL_ID,
|
||||
getString(R.string.ongoing_notification_title),
|
||||
IMPORTANCE_NONE);
|
||||
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
nm.createNotificationChannel(ongoingChannel);
|
||||
NotificationChannel failureChannel = new NotificationChannel(
|
||||
FAILURE_CHANNEL_ID,
|
||||
getString(R.string.startup_failed_notification_title),
|
||||
IMPORTANCE_DEFAULT);
|
||||
failureChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
nm.createNotificationChannel(failureChannel);
|
||||
// Create mandatory notification channel
|
||||
String channelId = "foregroundService";
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
NotificationChannel channel = new NotificationChannel(channelId,
|
||||
getString(R.string.app_name),
|
||||
NotificationManager.IMPORTANCE_NONE);
|
||||
channel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
NotificationManager nm =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.createNotificationChannel(channel);
|
||||
}
|
||||
// Show an ongoing notification that the service is running
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(this, ONGOING_CHANNEL_ID);
|
||||
new NotificationCompat.Builder(this, channelId);
|
||||
b.setSmallIcon(R.drawable.notification_ongoing);
|
||||
b.setColor(ContextCompat.getColor(this, R.color.briar_primary));
|
||||
b.setContentTitle(getText(R.string.ongoing_notification_title));
|
||||
@@ -131,50 +97,34 @@ public class BriarService extends Service {
|
||||
Intent i = new Intent(this, NavDrawerActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
|
||||
if (SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
b.setCategory(CATEGORY_SERVICE);
|
||||
b.setVisibility(VISIBILITY_SECRET);
|
||||
}
|
||||
b.setPriority(PRIORITY_MIN);
|
||||
startForeground(ONGOING_NOTIFICATION_ID, b.build());
|
||||
// Start the services in a background thread
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
String nickname = databaseConfig.getLocalAuthorName();
|
||||
StartResult result = lifecycleManager.startServices(nickname);
|
||||
if (result == SUCCESS) {
|
||||
started = true;
|
||||
} else if (result == ALREADY_RUNNING) {
|
||||
LOG.info("Already running");
|
||||
stopSelf();
|
||||
} else {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Startup failed: " + result);
|
||||
showStartupFailureNotification(result);
|
||||
stopSelf();
|
||||
}
|
||||
new Thread(() -> {
|
||||
String nickname = databaseConfig.getLocalAuthorName();
|
||||
StartResult result = lifecycleManager.startServices(nickname);
|
||||
if (result == SUCCESS) {
|
||||
started = true;
|
||||
} else if (result == ALREADY_RUNNING) {
|
||||
LOG.info("Already running");
|
||||
stopSelf();
|
||||
} else {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Startup failed: " + result);
|
||||
showStartupFailureNotification(result);
|
||||
stopSelf();
|
||||
}
|
||||
}.start();
|
||||
// Register for device shutdown broadcasts
|
||||
receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
LOG.info("Device is shutting down");
|
||||
shutdownFromBackground();
|
||||
}
|
||||
};
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_SHUTDOWN);
|
||||
filter.addAction("android.intent.action.QUICKBOOT_POWEROFF");
|
||||
filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF");
|
||||
registerReceiver(receiver, filter);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void showStartupFailureNotification(StartResult result) {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
||||
BriarService.this, FAILURE_CHANNEL_ID);
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(BriarService.this);
|
||||
b.setSmallIcon(android.R.drawable.stat_notify_error);
|
||||
b.setContentTitle(getText(
|
||||
R.string.startup_failed_notification_title));
|
||||
@@ -183,8 +133,9 @@ public class BriarService extends Service {
|
||||
Intent i = new Intent(BriarService.this,
|
||||
StartupFailureActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
i.putExtra(EXTRA_START_RESULT, result);
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, FAILURE_NOTIFICATION_ID);
|
||||
i.putExtra("briar.START_RESULT", result);
|
||||
i.putExtra("briar.FAILURE_NOTIFICATION_ID",
|
||||
FAILURE_NOTIFICATION_ID);
|
||||
b.setContentIntent(PendingIntent.getActivity(BriarService.this,
|
||||
0, i, FLAG_UPDATE_CURRENT));
|
||||
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||
@@ -193,7 +144,7 @@ public class BriarService extends Service {
|
||||
// Bring the dashboard to the front to clear the back stack
|
||||
i = new Intent(BriarService.this, NavDrawerActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i.putExtra(EXTRA_STARTUP_FAILED, true);
|
||||
i.putExtra("briar.STARTUP_FAILED", true);
|
||||
startActivity(i);
|
||||
});
|
||||
}
|
||||
@@ -213,62 +164,17 @@ public class BriarService extends Service {
|
||||
super.onDestroy();
|
||||
LOG.info("Destroyed");
|
||||
stopForeground(true);
|
||||
if (receiver != null) unregisterReceiver(receiver);
|
||||
// Stop the services in a background thread
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (started) lifecycleManager.stopServices();
|
||||
}
|
||||
}.start();
|
||||
new Thread(() -> {
|
||||
if (started) lifecycleManager.stopServices();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
LOG.warning("Memory is low");
|
||||
shutdownFromBackground();
|
||||
showLowMemoryShutdownNotification();
|
||||
}
|
||||
|
||||
private void shutdownFromBackground() {
|
||||
// Stop the service
|
||||
stopSelf();
|
||||
// Hide the UI
|
||||
Intent i = new Intent(this, HideUiActivity.class);
|
||||
i.addFlags(FLAG_ACTIVITY_NEW_TASK
|
||||
| FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
|
||||
| FLAG_ACTIVITY_NO_ANIMATION
|
||||
| FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(i);
|
||||
// Wait for shutdown to complete, then exit
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (started) lifecycleManager.waitForShutdown();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for shutdown");
|
||||
}
|
||||
LOG.info("Exiting");
|
||||
System.exit(0);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void showLowMemoryShutdownNotification() {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
||||
BriarService.this, FAILURE_CHANNEL_ID);
|
||||
b.setSmallIcon(android.R.drawable.stat_notify_error);
|
||||
b.setContentTitle(getText(
|
||||
R.string.low_memory_shutdown_notification_title));
|
||||
b.setContentText(getText(
|
||||
R.string.low_memory_shutdown_notification_text));
|
||||
Intent i = new Intent(this, SplashScreenActivity.class);
|
||||
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
|
||||
b.setAutoCancel(true);
|
||||
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(FAILURE_NOTIFICATION_ID, b.build());
|
||||
});
|
||||
// FIXME: Work out what to do about it
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
@@ -25,33 +16,23 @@ import java.io.InputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
||||
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
||||
import static android.content.Intent.ACTION_PACKAGE_REPLACED;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
|
||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||
import static android.content.pm.PackageManager.GET_SIGNATURES;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@NotNullByDefault
|
||||
class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
||||
class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
||||
@@ -75,93 +56,54 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
||||
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
|
||||
"0B145B6AA192858E79020103";
|
||||
|
||||
private static final String PREF_KEY_ALLOWED = "allowedOverlayApps";
|
||||
|
||||
private final PackageManager pm;
|
||||
private final Application app;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final SharedPreferences prefs;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
// UiThread
|
||||
@Nullable
|
||||
private BroadcastReceiver receiver = null;
|
||||
|
||||
// UiThread
|
||||
@Nullable
|
||||
private Collection<AppDetails> cachedApps = null;
|
||||
|
||||
@Inject
|
||||
ScreenFilterMonitorImpl(Application app, AndroidExecutor androidExecutor,
|
||||
SharedPreferences prefs) {
|
||||
ScreenFilterMonitorImpl(Application app) {
|
||||
pm = app.getPackageManager();
|
||||
this.app = app;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.prefs = prefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public Collection<AppDetails> getApps() {
|
||||
if (cachedApps != null) return cachedApps;
|
||||
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
|
||||
Collections.emptySet());
|
||||
List<AppDetails> apps = new ArrayList<>();
|
||||
public Set<String> getApps() {
|
||||
Set<String> screenFilterApps = new TreeSet<>();
|
||||
List<PackageInfo> packageInfos =
|
||||
pm.getInstalledPackages(GET_PERMISSIONS);
|
||||
for (PackageInfo packageInfo : packageInfos) {
|
||||
if (!allowed.contains(packageInfo.packageName)
|
||||
&& isOverlayApp(packageInfo)) {
|
||||
String name = getAppName(packageInfo);
|
||||
apps.add(new AppDetails(name, packageInfo.packageName));
|
||||
if (isOverlayApp(packageInfo)) {
|
||||
String name = pkgToString(packageInfo);
|
||||
if (name != null) {
|
||||
screenFilterApps.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Collections.sort(apps, (a, b) -> a.name.compareTo(b.name));
|
||||
apps = Collections.unmodifiableList(apps);
|
||||
cachedApps = apps;
|
||||
return apps;
|
||||
return screenFilterApps;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void allowApps(Collection<String> packageNames) {
|
||||
cachedApps = null;
|
||||
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
|
||||
Collections.emptySet());
|
||||
Set<String> merged = new HashSet<>(allowed);
|
||||
merged.addAll(packageNames);
|
||||
prefs.edit().putStringSet(PREF_KEY_ALLOWED, merged).apply();
|
||||
}
|
||||
|
||||
// Returns the application name for a given package, or the package name
|
||||
// if no application name is available
|
||||
private String getAppName(PackageInfo pkgInfo) {
|
||||
// Fetches the application name for a given package.
|
||||
@Nullable
|
||||
private String pkgToString(PackageInfo pkgInfo) {
|
||||
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
||||
return seq == null ? pkgInfo.packageName : seq.toString();
|
||||
if (seq != null) {
|
||||
return seq.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checks if an installed package is a user app using the permission.
|
||||
private boolean isOverlayApp(PackageInfo packageInfo) {
|
||||
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
|
||||
// Ignore system apps
|
||||
if ((packageInfo.applicationInfo.flags & mask) != 0) return false;
|
||||
if ((packageInfo.applicationInfo.flags & mask) != 0) {
|
||||
return false;
|
||||
}
|
||||
// Ignore Play Services, it's effectively a system app
|
||||
if (isPlayServices(packageInfo.packageName)) return false;
|
||||
if (isPlayServices(packageInfo.packageName)) {
|
||||
return false;
|
||||
}
|
||||
// Get permissions
|
||||
String[] requestedPermissions = packageInfo.requestedPermissions;
|
||||
if (requestedPermissions == null) return false;
|
||||
if (SDK_INT >= 16 && SDK_INT < 23) {
|
||||
// Check whether the permission has been requested and granted
|
||||
int[] flags = packageInfo.requestedPermissionsFlags;
|
||||
for (int i = 0; i < requestedPermissions.length; i++) {
|
||||
if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)) {
|
||||
// 'flags' may be null on Robolectric
|
||||
return flags == null ||
|
||||
(flags[i] & REQUESTED_PERMISSION_GRANTED) != 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check whether the permission has been requested
|
||||
if (requestedPermissions != null) {
|
||||
for (String requestedPermission : requestedPermissions) {
|
||||
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
|
||||
return true;
|
||||
@@ -171,7 +113,6 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("PackageManagerGetSignatures")
|
||||
private boolean isPlayServices(String pkg) {
|
||||
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
|
||||
try {
|
||||
@@ -194,36 +135,4 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_PACKAGE_ADDED);
|
||||
filter.addAction(ACTION_PACKAGE_CHANGED);
|
||||
filter.addAction(ACTION_PACKAGE_REMOVED);
|
||||
filter.addAction(ACTION_PACKAGE_REPLACED);
|
||||
filter.addDataScheme("package");
|
||||
receiver = new PackageBroadcastReceiver();
|
||||
app.registerReceiver(receiver, filter);
|
||||
cachedApps = null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
if (receiver != null) app.unregisterReceiver(receiver);
|
||||
});
|
||||
}
|
||||
|
||||
private class PackageBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
cachedApps = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,37 +3,32 @@ package org.briarproject.briar.android;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||
|
||||
public class StartupFailureActivity extends BaseActivity implements
|
||||
BaseFragmentListener {
|
||||
public class StartupFailureActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
setContentView(R.layout.activity_startup_failure);
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
}
|
||||
|
||||
private void handleIntent(Intent i) {
|
||||
StartResult result =
|
||||
(StartResult) i.getSerializableExtra(EXTRA_START_RESULT);
|
||||
int notificationId = i.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
|
||||
StartResult result = (StartResult) i.getSerializableExtra("briar.START_RESULT");
|
||||
int notificationId = i.getIntExtra("briar.FAILURE_NOTIFICATION_ID", -1);
|
||||
|
||||
// cancel notification
|
||||
if (notificationId > -1) {
|
||||
@@ -43,31 +38,12 @@ public class StartupFailureActivity extends BaseActivity implements
|
||||
}
|
||||
|
||||
// show proper error message
|
||||
String errorMsg;
|
||||
switch (result) {
|
||||
case DATA_TOO_OLD_ERROR:
|
||||
errorMsg = getString(R.string.startup_failed_db_error);
|
||||
break;
|
||||
case DATA_TOO_NEW_ERROR:
|
||||
errorMsg =
|
||||
getString(R.string.startup_failed_data_too_new_error);
|
||||
break;
|
||||
case DB_ERROR:
|
||||
errorMsg =
|
||||
getString(R.string.startup_failed_data_too_old_error);
|
||||
break;
|
||||
case SERVICE_ERROR:
|
||||
errorMsg = getString(R.string.startup_failed_service_error);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
TextView view = findViewById(R.id.errorView);
|
||||
if (result.equals(StartResult.DB_ERROR)) {
|
||||
view.setText(getText(R.string.startup_failed_db_error));
|
||||
} else if (result.equals(StartResult.SERVICE_ERROR)) {
|
||||
view.setText(getText(R.string.startup_failed_service_error));
|
||||
}
|
||||
showInitialFragment(ErrorFragment.newInstance(errorMsg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnDbThread(Runnable runnable) {
|
||||
throw new AssertionError("Deprecated and should not be used");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public interface TestingConstants {
|
||||
* Whether this is a beta build. This should be set to false for final
|
||||
* release builds.
|
||||
*/
|
||||
boolean IS_BETA_BUILD = true;
|
||||
boolean IS_BETA_BUILD = false;
|
||||
|
||||
/**
|
||||
* Default log level. Disable logging for final release builds.
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.activity;
|
||||
import android.app.Activity;
|
||||
|
||||
import org.briarproject.briar.android.AndroidComponent;
|
||||
import org.briarproject.briar.android.StartupFailureActivity;
|
||||
import org.briarproject.briar.android.blog.BlogActivity;
|
||||
import org.briarproject.briar.android.blog.BlogFragment;
|
||||
import org.briarproject.briar.android.blog.BlogModule;
|
||||
@@ -22,7 +21,6 @@ import org.briarproject.briar.android.forum.CreateForumActivity;
|
||||
import org.briarproject.briar.android.forum.ForumActivity;
|
||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||
@@ -32,7 +30,6 @@ import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
|
||||
import org.briarproject.briar.android.login.AuthorNameFragment;
|
||||
import org.briarproject.briar.android.login.ChangePasswordActivity;
|
||||
import org.briarproject.briar.android.login.DozeFragment;
|
||||
import org.briarproject.briar.android.login.OpenDatabaseActivity;
|
||||
import org.briarproject.briar.android.login.PasswordActivity;
|
||||
import org.briarproject.briar.android.login.PasswordFragment;
|
||||
import org.briarproject.briar.android.login.SetupActivity;
|
||||
@@ -89,8 +86,6 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(SetupActivity activity);
|
||||
|
||||
void inject(OpenDatabaseActivity activity);
|
||||
|
||||
void inject(NavDrawerActivity activity);
|
||||
|
||||
void inject(PasswordActivity activity);
|
||||
@@ -155,13 +150,9 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(RssFeedManageActivity activity);
|
||||
|
||||
void inject(StartupFailureActivity activity);
|
||||
|
||||
// Fragments
|
||||
void inject(AuthorNameFragment fragment);
|
||||
|
||||
void inject(PasswordFragment fragment);
|
||||
|
||||
void inject(DozeFragment fragment);
|
||||
|
||||
void inject(ContactListFragment fragment);
|
||||
@@ -198,5 +189,4 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(SettingsFragment fragment);
|
||||
|
||||
void inject(ScreenFilterDialogFragment fragment);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.activity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.briarproject.briar.android.controller.BriarController;
|
||||
import org.briarproject.briar.android.controller.BriarControllerImpl;
|
||||
@@ -18,6 +19,7 @@ import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
|
||||
|
||||
@Module
|
||||
@@ -55,6 +57,12 @@ public class ActivityModule {
|
||||
return configController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
SharedPreferences provideSharedPreferences(Activity activity) {
|
||||
return activity.getSharedPreferences("db", MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
PasswordController providePasswordController(
|
||||
|
||||
@@ -4,8 +4,6 @@ import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
@@ -22,15 +20,13 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
|
||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
@@ -51,10 +47,7 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
private final List<ActivityLifecycleController> lifecycleControllers =
|
||||
new ArrayList<>();
|
||||
private boolean destroyed = false;
|
||||
|
||||
@Nullable
|
||||
private Toolbar toolbar = null;
|
||||
private boolean searchedForToolbar = false;
|
||||
private ScreenFilterDialogFragment dialogFrag;
|
||||
|
||||
public abstract void injectActivity(ActivityComponent component);
|
||||
|
||||
@@ -63,8 +56,8 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||
|
||||
@@ -103,16 +96,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||
alc.onActivityStart();
|
||||
}
|
||||
protectToolbar();
|
||||
ScreenFilterDialogFragment f = findDialogFragment();
|
||||
if (f != null) f.setDismissListener(this::protectToolbar);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ScreenFilterDialogFragment findDialogFragment() {
|
||||
Fragment f = getSupportFragmentManager().findFragmentByTag(
|
||||
ScreenFilterDialogFragment.TAG);
|
||||
return (ScreenFilterDialogFragment) f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,6 +106,15 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (dialogFrag != null) {
|
||||
dialogFrag.dismiss();
|
||||
dialogFrag = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void showInitialFragment(BaseFragment f) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||
@@ -139,27 +131,14 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
.commit();
|
||||
}
|
||||
|
||||
private boolean showScreenFilterWarning() {
|
||||
// If the dialog is already visible, filter the tap
|
||||
ScreenFilterDialogFragment f = findDialogFragment();
|
||||
if (f != null && f.isVisible()) return false;
|
||||
Collection<AppDetails> apps = screenFilterMonitor.getApps();
|
||||
// If all overlay apps have been allowed, allow the tap
|
||||
if (apps.isEmpty()) return true;
|
||||
// Show dialog unless onSaveInstanceState() has been called, see #1112
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (!fm.isStateSaved()) {
|
||||
// Create dialog
|
||||
f = ScreenFilterDialogFragment.newInstance(apps);
|
||||
// When dialog is dismissed, update protection of toolbar
|
||||
f.setDismissListener(this::protectToolbar);
|
||||
// Hide soft keyboard when (re)showing dialog
|
||||
View focus = getCurrentFocus();
|
||||
if (focus != null) hideSoftKeyboard(focus);
|
||||
f.show(fm, ScreenFilterDialogFragment.TAG);
|
||||
}
|
||||
// Filter the tap
|
||||
return false;
|
||||
private void showScreenFilterWarning() {
|
||||
if (dialogFrag != null && dialogFrag.isVisible()) return;
|
||||
Set<String> apps = screenFilterMonitor.getApps();
|
||||
if (apps.isEmpty()) return;
|
||||
dialogFrag =
|
||||
ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps));
|
||||
dialogFrag.setCancelable(false);
|
||||
dialogFrag.show(getSupportFragmentManager(), dialogFrag.getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -213,25 +192,15 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
* is outside the wrapper.
|
||||
*/
|
||||
private void protectToolbar() {
|
||||
findToolbar();
|
||||
if (toolbar != null) {
|
||||
boolean filter = !screenFilterMonitor.getApps().isEmpty();
|
||||
UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
|
||||
}
|
||||
}
|
||||
|
||||
private void findToolbar() {
|
||||
if (searchedForToolbar) return;
|
||||
View decorView = getWindow().getDecorView();
|
||||
if (decorView instanceof ViewGroup)
|
||||
toolbar = findToolbar((ViewGroup) decorView);
|
||||
searchedForToolbar = true;
|
||||
if (decorView instanceof ViewGroup) {
|
||||
Toolbar toolbar = findToolbar((ViewGroup) decorView);
|
||||
if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Toolbar findToolbar(ViewGroup vg) {
|
||||
// Views inside tap-safe layouts are already protected
|
||||
if (vg instanceof TapSafeFrameLayout) return null;
|
||||
for (int i = 0, len = vg.getChildCount(); i < len; i++) {
|
||||
View child = vg.getChildAt(i);
|
||||
if (child instanceof Toolbar) return (Toolbar) child;
|
||||
@@ -251,20 +220,23 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
@Override
|
||||
public void setContentView(View v) {
|
||||
super.setContentView(makeTapSafeWrapper(v));
|
||||
protectToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentView(View v, LayoutParams layoutParams) {
|
||||
super.setContentView(makeTapSafeWrapper(v), layoutParams);
|
||||
protectToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addContentView(View v, LayoutParams layoutParams) {
|
||||
super.addContentView(makeTapSafeWrapper(v), layoutParams);
|
||||
protectToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldAllowTap() {
|
||||
return showScreenFilterWarning();
|
||||
public void onTapFiltered() {
|
||||
showScreenFilterWarning();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.briarproject.briar.android.controller.BriarController;
|
||||
import org.briarproject.briar.android.controller.DbController;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.briar.android.login.PasswordActivity;
|
||||
import org.briarproject.briar.android.logout.ExitActivity;
|
||||
import org.briarproject.briar.android.panic.ExitActivity;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -31,11 +31,11 @@ import static android.os.Build.VERSION.SDK_INT;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||
|
||||
@SuppressLint("Registered")
|
||||
public abstract class BriarActivity extends BaseActivity {
|
||||
|
||||
public static final String KEY_STARTUP_FAILED = "briar.STARTUP_FAILED";
|
||||
public static final String GROUP_ID = "briar.GROUP_ID";
|
||||
public static final String GROUP_NAME = "briar.GROUP_NAME";
|
||||
|
||||
@@ -79,10 +79,6 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
|
||||
public void setSceneTransitionAnimation() {
|
||||
if (SDK_INT < 21) return;
|
||||
// workaround for #1007
|
||||
if (isSamsung7()) {
|
||||
return;
|
||||
}
|
||||
Transition slide = new Slide(Gravity.RIGHT);
|
||||
slide.excludeTarget(android.R.id.statusBarBackground, true);
|
||||
slide.excludeTarget(android.R.id.navigationBarBackground, true);
|
||||
@@ -130,8 +126,8 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
b.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> dialog.dismiss());
|
||||
b.setOnDismissListener(dialog -> {
|
||||
CheckBox checkBox =
|
||||
((AlertDialog) dialog).findViewById(R.id.checkbox);
|
||||
CheckBox checkBox = (CheckBox) ((AlertDialog) dialog)
|
||||
.findViewById(R.id.checkbox);
|
||||
if (checkBox.isChecked())
|
||||
briarController.doNotAskAgainForDozeWhiteListing();
|
||||
});
|
||||
|
||||
@@ -11,6 +11,5 @@ public interface RequestCodes {
|
||||
int REQUEST_RINGTONE = 7;
|
||||
int REQUEST_PERMISSION_CAMERA = 8;
|
||||
int REQUEST_DOZE_WHITELISTING = 9;
|
||||
int REQUEST_ENABLE_BLUETOOTH = 10;
|
||||
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
getContext().startActivity(i);
|
||||
}
|
||||
}, getFragmentManager());
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
adapter =
|
||||
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
||||
adapter = new BlogPostAdapter(getActivity(), this);
|
||||
list = v.findViewById(R.id.postList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.BriarAdapter;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class BlogPostAdapter extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
||||
class BlogPostAdapter
|
||||
extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
||||
|
||||
private final OnBlogPostClickListener listener;
|
||||
@Nullable
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener,
|
||||
@Nullable FragmentManager fragmentManager) {
|
||||
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener) {
|
||||
super(ctx, BlogPostItem.class);
|
||||
this.listener = listener;
|
||||
this.fragmentManager = fragmentManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -32,7 +23,8 @@ class BlogPostAdapter extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_blog_post, parent, false);
|
||||
return new BlogPostViewHolder(v, false, listener, fragmentManager);
|
||||
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
|
||||
return ui;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Spanned;
|
||||
@@ -35,7 +34,6 @@ import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
||||
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
||||
|
||||
@@ -53,16 +51,12 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@NonNull
|
||||
private final OnBlogPostClickListener listener;
|
||||
@Nullable
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
BlogPostViewHolder(View v, boolean fullText,
|
||||
@NonNull OnBlogPostClickListener listener,
|
||||
@Nullable FragmentManager fragmentManager) {
|
||||
@NonNull OnBlogPostClickListener listener) {
|
||||
super(v);
|
||||
this.fullText = fullText;
|
||||
this.listener = listener;
|
||||
this.fragmentManager = fragmentManager;
|
||||
|
||||
ctx = v.getContext();
|
||||
layout = v.findViewById(R.id.postLayout);
|
||||
@@ -122,7 +116,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
if (fullText) {
|
||||
body.setText(bodyText);
|
||||
body.setTextIsSelectable(true);
|
||||
makeLinksClickable(body, fragmentManager);
|
||||
makeLinksClickable(body);
|
||||
} else {
|
||||
body.setTextIsSelectable(false);
|
||||
if (bodyText.length() > TEASER_LENGTH)
|
||||
@@ -136,7 +130,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
||||
i.putExtra(POST_ID, item.getId().getBytes());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23 && !isSamsung7()) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
ActivityOptionsCompat options =
|
||||
makeSceneTransitionAnimation((Activity) ctx, layout,
|
||||
getTransitionName(item.getId()));
|
||||
@@ -144,7 +138,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
options.toBundle());
|
||||
} else {
|
||||
// work-around for android bug #224270
|
||||
// work-around for Samsung Android 7 bug #1007
|
||||
ctx.startActivity(i);
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user