mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
6 Commits
introducti
...
bluetooth-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbb65ec807 | ||
|
|
53b38d83e8 | ||
|
|
692db742cf | ||
|
|
5b177c9901 | ||
|
|
c00bf2b2cd | ||
|
|
55150fe02a |
@@ -1,8 +1,6 @@
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
import de.undercouch.gradle.tasks.download.Verify
|
||||
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'witness'
|
||||
apply plugin: 'de.undercouch.download'
|
||||
@@ -69,68 +67,30 @@ def torBinaries = [
|
||||
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||
]
|
||||
|
||||
def verifyOrDeleteBinary(name, chksum, alreadyVerified) {
|
||||
return tasks.create("verifyOrDeleteBinary${name}", VerifyOrDelete) {
|
||||
src "${torBinaryDir}/${name}.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum chksum
|
||||
result alreadyVerified
|
||||
onlyIf {
|
||||
src.exists()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def downloadBinary(name, chksum, alreadyVerified) {
|
||||
return tasks.create([
|
||||
name: "downloadBinary${name}",
|
||||
type: Download,
|
||||
dependsOn: verifyOrDeleteBinary(name, chksum, alreadyVerified)]) {
|
||||
def downloadBinary(name) {
|
||||
return tasks.create("downloadBinary${name}", Download) {
|
||||
src "${torDownloadUrl}${name}.zip"
|
||||
.replace('tor_', "tor-${torVersion}-")
|
||||
.replace('geoip', "geoip-${geoipVersion}")
|
||||
.replaceAll('_', '-')
|
||||
dest "${torBinaryDir}/${name}.zip"
|
||||
onlyIf {
|
||||
!dest.exists()
|
||||
}
|
||||
onlyIfNewer true
|
||||
}
|
||||
}
|
||||
|
||||
def verifyBinary(name, chksum) {
|
||||
boolean[] alreadyVerified = [false]
|
||||
return tasks.create([
|
||||
name : "verifyBinary${name}",
|
||||
type : Verify,
|
||||
dependsOn: downloadBinary(name, chksum, alreadyVerified)]) {
|
||||
dependsOn: downloadBinary(name)]) {
|
||||
src "${torBinaryDir}/${name}.zip"
|
||||
algorithm 'SHA-256'
|
||||
checksum chksum
|
||||
onlyIf {
|
||||
!alreadyVerified[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
torBinaries.every { name, checksum ->
|
||||
preBuild.dependsOn.add(verifyBinary(name, checksum))
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyOrDelete extends Verify {
|
||||
|
||||
boolean[] result
|
||||
|
||||
@TaskAction
|
||||
@Override
|
||||
void verify() throws IOException, NoSuchAlgorithmException {
|
||||
try {
|
||||
super.verify()
|
||||
result[0] = true
|
||||
} catch (Exception e) {
|
||||
println "${src} failed verification - deleting"
|
||||
src.delete()
|
||||
}
|
||||
torBinaries.every { key, value ->
|
||||
preBuild.dependsOn.add(verifyBinary(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public class AndroidPluginModule {
|
||||
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
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.briarproject.bramble.util.AndroidUtils;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -37,7 +39,16 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
|
||||
import static android.bluetooth.BluetoothDevice.BOND_BONDING;
|
||||
import static android.bluetooth.BluetoothDevice.BOND_NONE;
|
||||
import static android.bluetooth.BluetoothDevice.EXTRA_BOND_STATE;
|
||||
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
|
||||
import static android.bluetooth.BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -55,10 +66,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
AndroidBluetoothPlugin(BluetoothConnectionManager connectionManager,
|
||||
Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
super(connectionManager, ioExecutor, secureRandom, backoff, callback,
|
||||
maxLatency);
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
@@ -70,6 +83,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||
filter.addAction(ACTION_BOND_STATE_CHANGED);
|
||||
receiver = new BluetoothStateReceiver();
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
}
|
||||
@@ -154,7 +168,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||
return new AndroidBluetoothTransportConnection(this, s);
|
||||
return new AndroidBluetoothTransportConnection(this,
|
||||
connectionManager, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,6 +180,17 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
@Override
|
||||
DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
boolean found = false;
|
||||
List<String> addresses = new ArrayList<>();
|
||||
for (BluetoothDevice d : adapter.getBondedDevices()) {
|
||||
addresses.add(scrubMacAddress(d.getAddress()));
|
||||
if (d.getAddress().equals(address)) found = true;
|
||||
}
|
||||
LOG.info("Bonded devices: " + addresses);
|
||||
if (found) LOG.info("Connecting to bonded device");
|
||||
else LOG.info("Connecting to unbonded device");
|
||||
}
|
||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||
UUID u = UUID.fromString(uuid);
|
||||
BluetoothSocket s = null;
|
||||
@@ -190,16 +216,42 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
|
||||
@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");
|
||||
String action = intent.getAction();
|
||||
if (ACTION_STATE_CHANGED.equals(action)) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) onAdapterEnabled();
|
||||
else if (state == STATE_OFF) onAdapterDisabled();
|
||||
} else if (ACTION_SCAN_MODE_CHANGED.equals(action)) {
|
||||
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");
|
||||
}
|
||||
} else if (ACTION_BOND_STATE_CHANGED.equals(action)) {
|
||||
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Bond state changed for "
|
||||
+ scrubMacAddress(d.getAddress()));
|
||||
}
|
||||
int oldState = intent.getIntExtra(EXTRA_PREVIOUS_BOND_STATE, 0);
|
||||
if (oldState == BOND_NONE) {
|
||||
LOG.info("Old state: none");
|
||||
} else if (oldState == BOND_BONDING) {
|
||||
LOG.info("Old state: bonding");
|
||||
} else if (oldState == BOND_BONDED) {
|
||||
LOG.info("Old state: bonded");
|
||||
}
|
||||
int state = intent.getIntExtra(EXTRA_BOND_STATE, 0);
|
||||
if (state == BOND_NONE) {
|
||||
LOG.info("New state: none");
|
||||
} else if (state == BOND_BONDING) {
|
||||
LOG.info("New state: bonding");
|
||||
} else if (state == BOND_BONDED) {
|
||||
LOG.info("New state: bonded");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +59,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
BluetoothConnectionManager connectionManager =
|
||||
new BluetoothConnectionManagerImpl();
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
||||
connectionManager, ioExecutor, androidExecutor, appContext,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,14 @@ import java.io.OutputStream;
|
||||
class AndroidBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final BluetoothConnectionManager connectionManager;
|
||||
private final BluetoothSocket socket;
|
||||
|
||||
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
AndroidBluetoothTransportConnection(Plugin plugin,
|
||||
BluetoothConnectionManager connectionManager,
|
||||
BluetoothSocket socket) {
|
||||
super(plugin);
|
||||
this.connectionManager = connectionManager;
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@@ -33,6 +37,10 @@ class AndroidBluetoothTransportConnection
|
||||
|
||||
@Override
|
||||
protected void closeConnection(boolean exception) throws IOException {
|
||||
socket.close();
|
||||
try {
|
||||
socket.close();
|
||||
} finally {
|
||||
connectionManager.connectionClosed(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -64,6 +63,8 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@@ -110,7 +111,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(TorPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor, connectionStatusExecutor;
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
@@ -124,6 +125,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||
private final File doneFile, cookieFile;
|
||||
private final PowerManager.WakeLock wakeLock;
|
||||
private final Lock connectionStatusLock;
|
||||
private final AtomicReference<Future<?>> connectivityCheck =
|
||||
new AtomicReference<>();
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
@@ -165,9 +167,7 @@ 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);
|
||||
connectionStatusLock = new ReentrantLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -697,46 +697,56 @@ 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();
|
||||
boolean online = net != null && net.isConnected();
|
||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||
String country = locationUtils.getCurrentCountry();
|
||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||
country);
|
||||
Settings s = callback.getSettings();
|
||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
if ("".equals(country)) LOG.info("Country code unknown");
|
||||
else LOG.info("Country code: " + country);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
enableNetwork(false);
|
||||
} else if (blocked) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
enableNetwork(false);
|
||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||
LOG.info("Disabling network due to data setting");
|
||||
enableNetwork(false);
|
||||
} else {
|
||||
LOG.info("Enabling network");
|
||||
enableNetwork(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
connectionStatusLock.lock();
|
||||
updateConnectionStatusLocked();
|
||||
} finally {
|
||||
connectionStatusLock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Locking: connectionStatusLock
|
||||
private void updateConnectionStatusLocked() {
|
||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
boolean online = net != null && net.isConnected();
|
||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||
String country = locationUtils.getCurrentCountry();
|
||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||
country);
|
||||
Settings s = callback.getSettings();
|
||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||
if ("".equals(country)) LOG.info("Country code unknown");
|
||||
else LOG.info("Country code: " + country);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!online) {
|
||||
LOG.info("Disabling network, device is offline");
|
||||
enableNetwork(false);
|
||||
} else if (blocked) {
|
||||
LOG.info("Disabling network, country is blocked");
|
||||
enableNetwork(false);
|
||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||
LOG.info("Disabling network due to data setting");
|
||||
enableNetwork(false);
|
||||
} else {
|
||||
LOG.info("Enabling network");
|
||||
enableNetwork(true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleConnectionStatusUpdate() {
|
||||
Future<?> newConnectivityCheck =
|
||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||
@@ -778,7 +788,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() {
|
||||
|
||||
@@ -7,8 +7,6 @@ import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
@@ -90,10 +88,6 @@ public interface ClientHelper {
|
||||
BdfDictionary toDictionary(byte[] b, int off, int len)
|
||||
throws FormatException;
|
||||
|
||||
BdfDictionary toDictionary(TransportProperties transportProperties);
|
||||
|
||||
BdfDictionary toDictionary(Map<TransportId, TransportProperties> map);
|
||||
|
||||
BdfList toList(byte[] b, int off, int len) throws FormatException;
|
||||
|
||||
BdfList toList(byte[] b) throws FormatException;
|
||||
@@ -105,15 +99,8 @@ public interface ClientHelper {
|
||||
byte[] sign(String label, BdfList toSign, byte[] privateKey)
|
||||
throws FormatException, GeneralSecurityException;
|
||||
|
||||
void verifySignature(byte[] signature, String label, BdfList signed,
|
||||
byte[] publicKey) throws FormatException, GeneralSecurityException;
|
||||
void verifySignature(String label, byte[] sig, byte[] publicKey,
|
||||
BdfList signed) throws FormatException, GeneralSecurityException;
|
||||
|
||||
Author parseAndValidateAuthor(BdfList author) throws FormatException;
|
||||
|
||||
TransportProperties parseAndValidateTransportProperties(
|
||||
BdfDictionary properties) throws FormatException;
|
||||
|
||||
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||
BdfDictionary properties) throws FormatException;
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for an integer that uniquely identifies a contact within
|
||||
* the scope of the local device.
|
||||
* the scope of a single node.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -14,28 +13,23 @@ import java.util.Collection;
|
||||
public interface ContactManager {
|
||||
|
||||
/**
|
||||
* Registers a hook to be called whenever a contact is added or removed.
|
||||
* This method should be called before
|
||||
* {@link LifecycleManager#startServices(String)}.
|
||||
* Registers a hook to be called whenever a contact is added.
|
||||
*/
|
||||
void registerContactHook(ContactHook hook);
|
||||
void registerAddContactHook(AddContactHook hook);
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* derives and stores transport keys for each transport, and returns an ID
|
||||
* for the contact.
|
||||
* Registers a hook to be called whenever a contact is removed.
|
||||
*/
|
||||
void registerRemoveContactHook(RemoveContactHook hook);
|
||||
|
||||
/**
|
||||
* Stores a contact within the given transaction associated with the given
|
||||
* local and remote pseudonyms, and returns an ID for the contact.
|
||||
*/
|
||||
ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
SecretKey master, long timestamp, boolean alice, boolean verified,
|
||||
boolean active) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms
|
||||
* and returns an ID for the contact.
|
||||
*/
|
||||
ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
boolean verified, boolean active) throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a contact associated with the given local and remote pseudonyms,
|
||||
* and returns an ID for the contact.
|
||||
@@ -100,10 +94,11 @@ public interface ContactManager {
|
||||
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||
throws DbException;
|
||||
|
||||
interface ContactHook {
|
||||
|
||||
interface AddContactHook {
|
||||
void addingContact(Transaction txn, Contact c) throws DbException;
|
||||
}
|
||||
|
||||
interface RemoveContactHook {
|
||||
void removingContact(Transaction txn, Contact c) throws DbException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ public interface CryptoComponent {
|
||||
* signature created for another purpose
|
||||
* @return true if the signature was valid, false otherwise.
|
||||
*/
|
||||
boolean verifySignature(byte[] signature, String label, byte[] signed,
|
||||
byte[] publicKey) throws GeneralSecurityException;
|
||||
boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Returns the hash of the given inputs. The inputs are unambiguously
|
||||
@@ -91,18 +91,6 @@ public interface CryptoComponent {
|
||||
*/
|
||||
byte[] mac(String label, SecretKey macKey, byte[]... inputs);
|
||||
|
||||
/**
|
||||
* Verifies that the given message authentication code is valid for the
|
||||
* given secret key and inputs.
|
||||
*
|
||||
* @param label a namespaced label indicating the purpose of this MAC, to
|
||||
* prevent it from being repurposed or colliding with a MAC created for
|
||||
* another purpose
|
||||
* @return true if the MAC was valid, false otherwise.
|
||||
*/
|
||||
boolean verifyMac(byte[] mac, String label, SecretKey macKey,
|
||||
byte[]... inputs);
|
||||
|
||||
/**
|
||||
* Encrypts and authenticates the given plaintext so it can be written to
|
||||
* storage. The encryption and authentication keys are derived from the
|
||||
|
||||
@@ -16,10 +16,4 @@ public interface CryptoConstants {
|
||||
* The maximum length of a signature in bytes.
|
||||
*/
|
||||
int MAX_SIGNATURE_BYTES = 64;
|
||||
|
||||
/**
|
||||
* The length of a MAC in bytes.
|
||||
*/
|
||||
int MAC_BYTES = SecretKey.LENGTH;
|
||||
|
||||
}
|
||||
|
||||
@@ -14,10 +14,9 @@ public interface TransportCrypto {
|
||||
* rotation period from the given master secret.
|
||||
*
|
||||
* @param alice whether the keys are for use by Alice or Bob.
|
||||
* @param active whether the keys are usable for outgoing streams.
|
||||
*/
|
||||
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
||||
long rotationPeriod, boolean alice, boolean active);
|
||||
long rotationPeriod, boolean alice);
|
||||
|
||||
/**
|
||||
* Rotates the given transport keys to the given rotation period. If the
|
||||
|
||||
@@ -34,7 +34,7 @@ public class BdfDictionary extends TreeMap<String, Object> {
|
||||
super();
|
||||
}
|
||||
|
||||
public BdfDictionary(Map<String, ?> m) {
|
||||
public BdfDictionary(Map<String, Object> m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -44,7 +43,7 @@ public interface DatabaseComponent {
|
||||
* @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.
|
||||
@@ -104,17 +103,10 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, optionally binding them to the given
|
||||
* contact, and returns a key set ID.
|
||||
* Stores transport keys for a newly added contact.
|
||||
*/
|
||||
KeySetId addTransportKeys(Transaction txn, @Nullable ContactId c,
|
||||
TransportKeys k) throws DbException;
|
||||
|
||||
/**
|
||||
* Binds the given keys for the given transport to the given contact.
|
||||
*/
|
||||
void bindTransportKeys(Transaction txn, ContactId c, TransportId t,
|
||||
KeySetId k) throws DbException;
|
||||
void addTransportKeys(Transaction txn, ContactId c, TransportKeys k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if the database contains the given contact for the given
|
||||
@@ -267,30 +259,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
|
||||
@@ -347,8 +340,12 @@ public interface DatabaseComponent {
|
||||
|
||||
/**
|
||||
* Returns the IDs and states of all dependencies of the given message.
|
||||
* For missing dependencies and dependencies in other groups, the state
|
||||
* {@link State UNKNOWN} is returned.
|
||||
* Missing dependencies have the state
|
||||
* {@link ValidationManager.State UNKNOWN}.
|
||||
* Dependencies in other groups have the state
|
||||
* {@link ValidationManager.State INVALID}.
|
||||
* Note that these states are not set on the dependencies themselves; the
|
||||
* returned states should only be taken in the context of the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -356,9 +353,9 @@ public interface DatabaseComponent {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs and states of all dependents of the given message.
|
||||
* Dependents in other groups are not returned. If the given message is
|
||||
* missing, no dependents are returned.
|
||||
* Returns all IDs of messages that depend on the given message.
|
||||
* Messages in other groups that declare a dependency on the given message
|
||||
* will be returned even though such dependencies are invalid.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -403,14 +400,15 @@ public interface DatabaseComponent {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<KeySet> getTransportKeys(Transaction txn, TransportId t)
|
||||
throws DbException;
|
||||
Map<ContactId, TransportKeys> getTransportKeys(Transaction txn,
|
||||
TransportId t) throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing stream counter for the given transport keys.
|
||||
* Increments the outgoing stream counter for the given contact and
|
||||
* transport in the given rotation period .
|
||||
*/
|
||||
void incrementStreamCounter(Transaction txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
void incrementStreamCounter(Transaction txn, ContactId c, TransportId t,
|
||||
long rotationPeriod) throws DbException;
|
||||
|
||||
/**
|
||||
* Merges the given metadata with the existing metadata for the given
|
||||
@@ -480,12 +478,6 @@ public interface DatabaseComponent {
|
||||
*/
|
||||
void removeTransport(Transaction txn, TransportId t) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the given transport keys from the database.
|
||||
*/
|
||||
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given contact as verified.
|
||||
*/
|
||||
@@ -521,21 +513,15 @@ public interface DatabaseComponent {
|
||||
Collection<MessageId> dependencies) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given key set and transport in the
|
||||
* Sets the reordering window for the given contact and transport in the
|
||||
* given rotation period.
|
||||
*/
|
||||
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
|
||||
void setReorderingWindow(Transaction txn, ContactId c, TransportId t,
|
||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given transport keys as usable for outgoing streams.
|
||||
*/
|
||||
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
*/
|
||||
void updateTransportKeys(Transaction txn, Collection<KeySet> keys)
|
||||
throws DbException;
|
||||
void updateTransportKeys(Transaction txn,
|
||||
Map<ContactId, TransportKeys> keys) throws DbException;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
package org.briarproject.bramble.api.plugin;
|
||||
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for a namespaced string that uniquely identifies a
|
||||
* transport plugin.
|
||||
* Type-safe wrapper for a string that uniquely identifies a transport plugin.
|
||||
*/
|
||||
public class TransportId {
|
||||
|
||||
/**
|
||||
* The maximum length of a transport identifier in UTF-8 bytes.
|
||||
* The maximum length of transport identifier in UTF-8 bytes.
|
||||
*/
|
||||
public static int MAX_TRANSPORT_ID_LENGTH = 100;
|
||||
public static int MAX_TRANSPORT_ID_LENGTH = 64;
|
||||
|
||||
private final String id;
|
||||
|
||||
public TransportId(String id) {
|
||||
int length = StringUtils.toUtf8(id).length;
|
||||
if (length == 0 || length > MAX_TRANSPORT_ID_LENGTH)
|
||||
byte[] b = id.getBytes(Charset.forName("UTF-8"));
|
||||
if (b.length == 0 || b.length > MAX_TRANSPORT_ID_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for a namespaced string that uniquely identifies a sync
|
||||
* client.
|
||||
* Wrapper for a name-spaced string that uniquely identifies a sync client.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class ClientId implements Comparable<ClientId> {
|
||||
|
||||
/**
|
||||
* The maximum length of a client identifier in UTF-8 bytes.
|
||||
*/
|
||||
public static int MAX_CLIENT_ID_LENGTH = 100;
|
||||
|
||||
private final String id;
|
||||
|
||||
public ClientId(String id) {
|
||||
int length = StringUtils.toUtf8(id).length;
|
||||
if (length == 0 || length > MAX_CLIENT_ID_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ public class Group {
|
||||
SHARED // The group is visible and messages are shared
|
||||
}
|
||||
|
||||
/**
|
||||
* The current version of the group format.
|
||||
*/
|
||||
public static final int FORMAT_VERSION = 1;
|
||||
|
||||
private final GroupId id;
|
||||
private final ClientId clientId;
|
||||
private final byte[] descriptor;
|
||||
|
||||
@@ -5,11 +5,6 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
|
||||
|
||||
public class Message {
|
||||
|
||||
/**
|
||||
* The current version of the message format.
|
||||
*/
|
||||
public static final int FORMAT_VERSION = 1;
|
||||
|
||||
private final MessageId id;
|
||||
private final GroupId groupId;
|
||||
private final long timestamp;
|
||||
|
||||
@@ -16,13 +16,7 @@ public class MessageId extends UniqueId {
|
||||
/**
|
||||
* Label for hashing messages to calculate their identifiers.
|
||||
*/
|
||||
public static final String ID_LABEL = "org.briarproject.bramble/MESSAGE_ID";
|
||||
|
||||
/**
|
||||
* Label for hashing blocks of messages.
|
||||
*/
|
||||
public static final String BLOCK_LABEL =
|
||||
"org.briarproject.bramble/MESSAGE_BLOCK";
|
||||
public static final String LABEL = "org.briarproject.bramble/MESSAGE_ID";
|
||||
|
||||
public MessageId(byte[] id) {
|
||||
super(id);
|
||||
|
||||
@@ -6,8 +6,6 @@ import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
@@ -18,51 +16,13 @@ public interface KeyManager {
|
||||
|
||||
/**
|
||||
* Informs the key manager that a new contact has been added. Derives and
|
||||
* stores a set of transport keys for communicating with the contact over
|
||||
* each transport.
|
||||
* <p/>
|
||||
* stores transport keys for communicating with the contact.
|
||||
* {@link StreamContext StreamContexts} for the contact can be created
|
||||
* after this method has returned.
|
||||
*/
|
||||
void addContact(Transaction txn, ContactId c, SecretKey master,
|
||||
long timestamp, boolean alice) throws DbException;
|
||||
|
||||
/**
|
||||
* Derives and stores a set of unbound transport keys for each transport
|
||||
* and returns the key set IDs.
|
||||
* <p/>
|
||||
* The keys must be bound before they can be used for incoming streams,
|
||||
* and also activated before they can be used for outgoing streams.
|
||||
*/
|
||||
Map<TransportId, KeySetId> addUnboundKeys(Transaction txn, SecretKey master,
|
||||
long timestamp, boolean alice) throws DbException;
|
||||
|
||||
/**
|
||||
* Binds the given transport keys to the given contact.
|
||||
*/
|
||||
void bindKeys(Transaction txn, ContactId c, Map<TransportId, KeySetId> keys)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given transport keys as usable for outgoing streams. Keys must
|
||||
* be bound before they are activated.
|
||||
*/
|
||||
void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the given transport keys, which must not have been bound, from
|
||||
* the manager and the database.
|
||||
*/
|
||||
void removeKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns true if we have keys that can be used for outgoing streams to
|
||||
* the given contact over the given transport.
|
||||
*/
|
||||
boolean canSendOutgoingStreams(ContactId c, TransportId t);
|
||||
|
||||
/**
|
||||
* Returns a {@link StreamContext} for sending a stream to the given
|
||||
* contact over the given transport, or null if an error occurs or the
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* A set of transport keys for communicating with a contact. If the keys have
|
||||
* not yet been bound to a contact, {@link #getContactId()}} returns null.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class KeySet {
|
||||
|
||||
private final KeySetId keySetId;
|
||||
@Nullable
|
||||
private final ContactId contactId;
|
||||
private final TransportKeys transportKeys;
|
||||
|
||||
public KeySet(KeySetId keySetId, @Nullable ContactId contactId,
|
||||
TransportKeys transportKeys) {
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.transportKeys = transportKeys;
|
||||
}
|
||||
|
||||
public KeySetId getKeySetId() {
|
||||
return keySetId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public TransportKeys getTransportKeys() {
|
||||
return transportKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return keySetId.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof KeySet && keySetId.equals(((KeySet) o).keySetId);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.briarproject.bramble.api.transport;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* Type-safe wrapper for an integer that uniquely identifies a set of transport
|
||||
* keys within the scope of the local device.
|
||||
* <p/>
|
||||
* Key sets created on a given device must have increasing identifiers.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class KeySetId {
|
||||
|
||||
private final int id;
|
||||
|
||||
public KeySetId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getInt() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof KeySetId && id == ((KeySetId) o).id;
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,18 @@ public class OutgoingKeys {
|
||||
|
||||
private final SecretKey tagKey, headerKey;
|
||||
private final long rotationPeriod, streamCounter;
|
||||
private final boolean active;
|
||||
|
||||
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||
long rotationPeriod, boolean active) {
|
||||
this(tagKey, headerKey, rotationPeriod, 0, active);
|
||||
long rotationPeriod) {
|
||||
this(tagKey, headerKey, rotationPeriod, 0);
|
||||
}
|
||||
|
||||
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||
long rotationPeriod, long streamCounter, boolean active) {
|
||||
long rotationPeriod, long streamCounter) {
|
||||
this.tagKey = tagKey;
|
||||
this.headerKey = headerKey;
|
||||
this.rotationPeriod = rotationPeriod;
|
||||
this.streamCounter = streamCounter;
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public SecretKey getTagKey() {
|
||||
@@ -41,8 +39,4 @@ public class OutgoingKeys {
|
||||
public long getStreamCounter() {
|
||||
return streamCounter;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ 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.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
@@ -18,18 +16,13 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.ClientId.MAX_CLIENT_ID_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;
|
||||
@@ -61,33 +54,6 @@ public class TestUtils {
|
||||
return getRandomBytes(UniqueId.LENGTH);
|
||||
}
|
||||
|
||||
public static ClientId getClientId() {
|
||||
return new ClientId(getRandomString(MAX_CLIENT_ID_LENGTH));
|
||||
}
|
||||
|
||||
public static TransportId getTransportId() {
|
||||
return new TransportId(getRandomString(MAX_TRANSPORT_ID_LENGTH));
|
||||
}
|
||||
|
||||
public static TransportProperties getTransportProperties(int number) {
|
||||
TransportProperties tp = new TransportProperties();
|
||||
for (int i = 0; i < number; i++) {
|
||||
tp.put(getRandomString(1 + random.nextInt(MAX_PROPERTY_LENGTH)),
|
||||
getRandomString(1 + random.nextInt(MAX_PROPERTY_LENGTH))
|
||||
);
|
||||
}
|
||||
return tp;
|
||||
}
|
||||
|
||||
public static Map<TransportId, TransportProperties> getTransportPropertiesMap(
|
||||
int number) {
|
||||
Map<TransportId, TransportProperties> map = new HashMap<>();
|
||||
for (int i = 0; i < number; i++) {
|
||||
map.put(getTransportId(), getTransportProperties(number));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static SecretKey getSecretKey() {
|
||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
@@ -39,8 +37,6 @@ import javax.inject.Inject;
|
||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
@@ -328,20 +324,6 @@ class ClientHelperImpl implements ClientHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary toDictionary(TransportProperties transportProperties) {
|
||||
return new BdfDictionary(transportProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary toDictionary(
|
||||
Map<TransportId, TransportProperties> map) {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
for (Entry<TransportId, TransportProperties> e : map.entrySet())
|
||||
d.put(e.getKey().getString(), new BdfDictionary(e.getValue()));
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfList toList(byte[] b, int off, int len) throws FormatException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
|
||||
@@ -381,10 +363,9 @@ class ClientHelperImpl implements ClientHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifySignature(byte[] signature, String label, BdfList signed,
|
||||
byte[] publicKey) throws FormatException, GeneralSecurityException {
|
||||
if (!crypto.verifySignature(signature, label, toByteArray(signed),
|
||||
publicKey)) {
|
||||
public void verifySignature(String label, byte[] sig, byte[] publicKey,
|
||||
BdfList signed) throws FormatException, GeneralSecurityException {
|
||||
if (!crypto.verify(label, toByteArray(signed), publicKey, sig)) {
|
||||
throw new GeneralSecurityException("Invalid signature");
|
||||
}
|
||||
}
|
||||
@@ -401,33 +382,4 @@ class ClientHelperImpl implements ClientHelper {
|
||||
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
||||
return authorFactory.createAuthor(formatVersion, name, publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportProperties parseAndValidateTransportProperties(
|
||||
BdfDictionary properties) throws FormatException {
|
||||
checkSize(properties, 0, MAX_PROPERTIES_PER_TRANSPORT);
|
||||
TransportProperties p = new TransportProperties();
|
||||
for (String key : properties.keySet()) {
|
||||
checkLength(key, 1, MAX_PROPERTY_LENGTH);
|
||||
String value = properties.getString(key);
|
||||
checkLength(value, 1, MAX_PROPERTY_LENGTH);
|
||||
p.put(key, value);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||
BdfDictionary properties) throws FormatException {
|
||||
Map<TransportId, TransportProperties> tpMap = new HashMap<>();
|
||||
for (String key : properties.keySet()) {
|
||||
TransportId transportId = new TransportId(key);
|
||||
TransportProperties transportProperties =
|
||||
parseAndValidateTransportProperties(
|
||||
properties.getDictionary(key));
|
||||
tpMap.put(transportId, transportProperties);
|
||||
}
|
||||
return tpMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -251,8 +251,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
||||
r.readListEnd();
|
||||
LOG.info("Received pseudonym");
|
||||
// Verify the signature
|
||||
if (!crypto.verifySignature(sig, SIGNING_LABEL_EXCHANGE, nonce,
|
||||
publicKey)) {
|
||||
if (!crypto.verify(SIGNING_LABEL_EXCHANGE, nonce, publicKey, sig)) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Invalid signature");
|
||||
throw new GeneralSecurityException();
|
||||
|
||||
@@ -27,37 +27,36 @@ class ContactManagerImpl implements ContactManager {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final KeyManager keyManager;
|
||||
private final List<ContactHook> hooks;
|
||||
private final List<AddContactHook> addHooks;
|
||||
private final List<RemoveContactHook> removeHooks;
|
||||
|
||||
@Inject
|
||||
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
|
||||
this.db = db;
|
||||
this.keyManager = keyManager;
|
||||
hooks = new CopyOnWriteArrayList<>();
|
||||
addHooks = new CopyOnWriteArrayList<>();
|
||||
removeHooks = new CopyOnWriteArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerContactHook(ContactHook hook) {
|
||||
hooks.add(hook);
|
||||
public void registerAddContactHook(AddContactHook hook) {
|
||||
addHooks.add(hook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRemoveContactHook(RemoveContactHook hook) {
|
||||
removeHooks.add(hook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
SecretKey master, long timestamp, boolean alice, boolean verified,
|
||||
SecretKey master,long timestamp, boolean alice, boolean verified,
|
||||
boolean active) throws DbException {
|
||||
ContactId c = db.addContact(txn, remote, local, verified, active);
|
||||
keyManager.addContact(txn, c, master, timestamp, alice);
|
||||
Contact contact = db.getContact(txn, c);
|
||||
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
||||
boolean verified, boolean active) throws DbException {
|
||||
ContactId c = db.addContact(txn, remote, local, verified, active);
|
||||
Contact contact = db.getContact(txn, c);
|
||||
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||
for (AddContactHook hook : addHooks)
|
||||
hook.addingContact(txn, contact);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -157,7 +156,7 @@ class ContactManagerImpl implements ContactManager {
|
||||
@Override
|
||||
public boolean contactExists(AuthorId remoteAuthorId,
|
||||
AuthorId localAuthorId) throws DbException {
|
||||
boolean exists;
|
||||
boolean exists = false;
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
exists = contactExists(txn, remoteAuthorId, localAuthorId);
|
||||
@@ -172,7 +171,8 @@ class ContactManagerImpl implements ContactManager {
|
||||
public void removeContact(Transaction txn, ContactId c)
|
||||
throws DbException {
|
||||
Contact contact = db.getContact(txn, c);
|
||||
for (ContactHook hook : hooks) hook.removingContact(txn, contact);
|
||||
for (RemoveContactHook hook : removeHooks)
|
||||
hook.removingContact(txn, contact);
|
||||
db.removeContact(txn, c);
|
||||
}
|
||||
|
||||
|
||||
@@ -205,12 +205,12 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifySignature(byte[] signature, String label,
|
||||
byte[] signed, byte[] publicKey) throws GeneralSecurityException {
|
||||
public boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException {
|
||||
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
|
||||
Signature sig = new EdSignature();
|
||||
sig.initVerify(key);
|
||||
updateSignature(sig, label, signed);
|
||||
updateSignature(sig, label, signedData);
|
||||
return sig.verify(signature);
|
||||
}
|
||||
|
||||
@@ -262,17 +262,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyMac(byte[] mac, String label, SecretKey macKey,
|
||||
byte[]... inputs) {
|
||||
byte[] expected = mac(label, macKey, inputs);
|
||||
if (mac.length != expected.length) return false;
|
||||
// Constant-time comparison
|
||||
int cmp = 0;
|
||||
for (int i = 0; i < mac.length; i++) cmp |= mac[i] ^ expected[i];
|
||||
return cmp == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encryptWithPassword(byte[] input, String password) {
|
||||
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
||||
|
||||
@@ -36,8 +36,7 @@ class TransportCryptoImpl implements TransportCrypto {
|
||||
|
||||
@Override
|
||||
public TransportKeys deriveTransportKeys(TransportId t,
|
||||
SecretKey master, long rotationPeriod, boolean alice,
|
||||
boolean active) {
|
||||
SecretKey master, long rotationPeriod, boolean alice) {
|
||||
// Keys for the previous period are derived from the master secret
|
||||
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
||||
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
||||
@@ -58,7 +57,7 @@ class TransportCryptoImpl implements TransportCrypto {
|
||||
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
||||
rotationPeriod + 1);
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
||||
rotationPeriod, active);
|
||||
rotationPeriod);
|
||||
// Collect and return the keys
|
||||
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
@@ -72,7 +71,6 @@ class TransportCryptoImpl implements TransportCrypto {
|
||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
long startPeriod = outCurr.getRotationPeriod();
|
||||
boolean active = outCurr.isActive();
|
||||
// Rotate the keys
|
||||
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
||||
inPrev = inCurr;
|
||||
@@ -82,7 +80,7 @@ class TransportCryptoImpl implements TransportCrypto {
|
||||
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
||||
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
||||
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
||||
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p, active);
|
||||
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
||||
}
|
||||
// Collect and return the keys
|
||||
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -21,8 +20,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.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -48,7 +45,7 @@ interface Database<T> {
|
||||
* @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
|
||||
@@ -107,11 +104,10 @@ interface Database<T> {
|
||||
@Nullable ContactId sender) throws DbException;
|
||||
|
||||
/**
|
||||
* Adds a dependency between two messages, where the dependent message is
|
||||
* in the given state.
|
||||
* Adds a dependency between two messages in the given group.
|
||||
*/
|
||||
void addMessageDependency(T txn, Message dependent, MessageId dependency,
|
||||
State dependentState) throws DbException;
|
||||
void addMessageDependency(T txn, GroupId g, MessageId dependent,
|
||||
MessageId dependency) throws DbException;
|
||||
|
||||
/**
|
||||
* Records that a message has been offered by the given contact.
|
||||
@@ -125,16 +121,9 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores the given transport keys, optionally binding them to the given
|
||||
* contact, and returns a key set ID.
|
||||
* Stores transport keys for a newly added contact.
|
||||
*/
|
||||
KeySetId addTransportKeys(T txn, @Nullable ContactId c, TransportKeys k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Binds the given keys for the given transport to the given contact.
|
||||
*/
|
||||
void bindTransportKeys(T txn, ContactId c, TransportId t, KeySetId k)
|
||||
void addTransportKeys(T txn, ContactId c, TransportKeys k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -302,8 +291,10 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Returns the IDs and states of all dependencies of the given message.
|
||||
* For missing dependencies and dependencies in other groups, the state
|
||||
* {@link State UNKNOWN} is returned.
|
||||
* Missing dependencies have the state {@link State UNKNOWN}.
|
||||
* Dependencies in other groups have the state {@link State INVALID}.
|
||||
* Note that these states are not set on the dependencies themselves; the
|
||||
* returned states should only be taken in the context of the given message.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -311,9 +302,9 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs and states of all dependents of the given message.
|
||||
* Dependents in other groups are not returned. If the given message is
|
||||
* missing, no dependents are returned.
|
||||
* Returns all IDs and states of all dependents of the given message.
|
||||
* Messages in other groups that declare a dependency on the given message
|
||||
* will be returned even though such dependencies are invalid.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
@@ -432,27 +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;
|
||||
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
@@ -495,14 +490,15 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<KeySet> getTransportKeys(T txn, TransportId t)
|
||||
Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Increments the outgoing stream counter for the given transport keys.
|
||||
* Increments the outgoing stream counter for the given contact and
|
||||
* transport in the given rotation period.
|
||||
*/
|
||||
void incrementStreamCounter(T txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
void incrementStreamCounter(T txn, ContactId c, TransportId t,
|
||||
long rotationPeriod) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given messages as not needing to be acknowledged to the
|
||||
@@ -592,12 +588,6 @@ interface Database<T> {
|
||||
*/
|
||||
void removeTransport(T txn, TransportId t) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the given transport keys from the database.
|
||||
*/
|
||||
void removeTransportKeys(T txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Resets the transmission count and expiry time of the given message with
|
||||
* respect to the given contact.
|
||||
@@ -633,18 +623,12 @@ interface Database<T> {
|
||||
void setMessageState(T txn, MessageId m, State state) throws DbException;
|
||||
|
||||
/**
|
||||
* Sets the reordering window for the given key set and transport in the
|
||||
* Sets the reordering window for the given contact and transport in the
|
||||
* given rotation period.
|
||||
*/
|
||||
void setReorderingWindow(T txn, KeySetId k, TransportId t,
|
||||
void setReorderingWindow(T txn, ContactId c, TransportId t,
|
||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
||||
|
||||
/**
|
||||
* Marks the given transport keys as usable for outgoing streams.
|
||||
*/
|
||||
void setTransportKeysActive(T txn, TransportId t, KeySetId k)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Updates the transmission count and expiry time of the given message
|
||||
* with respect to the given contact, using the latency of the transport
|
||||
@@ -656,5 +640,6 @@ interface Database<T> {
|
||||
/**
|
||||
* Stores the given transport keys, deleting any keys they have replaced.
|
||||
*/
|
||||
void updateTransportKeys(T txn, Collection<KeySet> keys) throws DbException;
|
||||
void updateTransportKeys(T txn, Map<ContactId, TransportKeys> keys)
|
||||
throws DbException;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -51,15 +50,15 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.logging.Logger;
|
||||
@@ -101,9 +100,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open(@Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
boolean reopened = db.open(listener);
|
||||
public boolean open() throws DbException {
|
||||
boolean reopened = db.open();
|
||||
shutdown.addShutdownHook(() -> {
|
||||
try {
|
||||
close();
|
||||
@@ -234,27 +232,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySetId addTransportKeys(Transaction transaction,
|
||||
@Nullable ContactId c, TransportKeys k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (c != null && !db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsTransport(txn, k.getTransportId()))
|
||||
throw new NoSuchTransportException();
|
||||
return db.addTransportKeys(txn, c, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTransportKeys(Transaction transaction, ContactId c,
|
||||
TransportId t, KeySetId k) throws DbException {
|
||||
public void addTransportKeys(Transaction transaction, ContactId c,
|
||||
TransportKeys k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsTransport(txn, t))
|
||||
if (!db.containsTransport(txn, k.getTransportId()))
|
||||
throw new NoSuchTransportException();
|
||||
db.bindTransportKeys(txn, c, t, k);
|
||||
db.addTransportKeys(txn, c, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -467,24 +453,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
|
||||
@@ -585,7 +571,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
@@ -598,8 +584,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<KeySet> getTransportKeys(Transaction transaction,
|
||||
TransportId t) throws DbException {
|
||||
public Map<ContactId, TransportKeys> getTransportKeys(
|
||||
Transaction transaction, TransportId t) throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
@@ -607,13 +593,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
public void incrementStreamCounter(Transaction transaction, ContactId c,
|
||||
TransportId t, long rotationPeriod) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.incrementStreamCounter(txn, t, k);
|
||||
db.incrementStreamCounter(txn, c, t, rotationPeriod);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -775,7 +763,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, m))
|
||||
throw new NoSuchMessageException();
|
||||
// TODO: Don't allow messages with dependents to be removed
|
||||
db.removeMessage(txn, m);
|
||||
}
|
||||
|
||||
@@ -789,16 +776,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.removeTransport(txn, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransportKeys(Transaction transaction,
|
||||
TransportId t, KeySetId k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.removeTransportKeys(txn, t, k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContactVerified(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
@@ -871,42 +848,38 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsMessage(txn, dependent.getId()))
|
||||
throw new NoSuchMessageException();
|
||||
State dependentState = db.getMessageState(txn, dependent.getId());
|
||||
for (MessageId dependency : dependencies) {
|
||||
db.addMessageDependency(txn, dependent, dependency, dependentState);
|
||||
db.addMessageDependency(txn, dependent.getGroupId(),
|
||||
dependent.getId(), dependency);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReorderingWindow(Transaction transaction, KeySetId k,
|
||||
public void setReorderingWindow(Transaction transaction, ContactId c,
|
||||
TransportId t, long rotationPeriod, long base, byte[] bitmap)
|
||||
throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsContact(txn, c))
|
||||
throw new NoSuchContactException();
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.setReorderingWindow(txn, k, t, rotationPeriod, base, bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransportKeysActive(Transaction transaction, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
if (!db.containsTransport(txn, t))
|
||||
throw new NoSuchTransportException();
|
||||
db.setTransportKeysActive(txn, t, k);
|
||||
db.setReorderingWindow(txn, c, t, rotationPeriod, base, bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportKeys(Transaction transaction,
|
||||
Collection<KeySet> keys) throws DbException {
|
||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||
T txn = unbox(transaction);
|
||||
Collection<KeySet> filtered = new ArrayList<>();
|
||||
for (KeySet ks : keys) {
|
||||
TransportId t = ks.getTransportKeys().getTransportId();
|
||||
if (db.containsTransport(txn, t)) filtered.add(ks);
|
||||
Map<ContactId, TransportKeys> filtered = new HashMap<>();
|
||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
TransportKeys k = e.getValue();
|
||||
if (db.containsContact(txn, c)
|
||||
&& db.containsTransport(txn, k.getTransportId())) {
|
||||
filtered.put(c, k);
|
||||
}
|
||||
}
|
||||
db.updateTransportKeys(txn, filtered);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -44,11 +42,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,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.sql.Statement;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
@@ -46,10 +44,10 @@ class HyperSqlDatabase 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.hsqldb.jdbc.JDBCDriver", reopen, listener);
|
||||
super.open("org.hsqldb.jdbc.JDBCDriver", reopen);
|
||||
return reopen;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
@@ -25,8 +24,6 @@ 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.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
|
||||
@@ -52,7 +49,6 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.sql.Types.INTEGER;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
@@ -60,6 +56,7 @@ 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;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
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;
|
||||
@@ -74,7 +71,7 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 36;
|
||||
static final int CODE_SCHEMA_VERSION = 35;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -172,10 +169,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " (groupId _HASH NOT NULL,"
|
||||
+ " messageId _HASH NOT NULL,"
|
||||
+ " dependencyId _HASH NOT NULL," // Not a foreign key
|
||||
+ " messageState INT NOT NULL," // Denormalised
|
||||
// Denormalised, null if dependency is missing or in a
|
||||
// different group
|
||||
+ " dependencyState INT,"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
@@ -225,44 +218,37 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " maxLatency INT NOT NULL,"
|
||||
+ " PRIMARY KEY (transportId))";
|
||||
|
||||
private static final String CREATE_OUTGOING_KEYS =
|
||||
"CREATE TABLE outgoingKeys"
|
||||
+ " (transportId _STRING NOT NULL,"
|
||||
+ " keySetId _COUNTER,"
|
||||
+ " rotationPeriod BIGINT NOT NULL,"
|
||||
+ " contactId INT," // Null if keys are not bound
|
||||
+ " tagKey _SECRET NOT NULL,"
|
||||
+ " headerKey _SECRET NOT NULL,"
|
||||
+ " stream BIGINT NOT NULL,"
|
||||
+ " active BOOLEAN NOT NULL,"
|
||||
+ " PRIMARY KEY (transportId, keySetId),"
|
||||
+ " FOREIGN KEY (transportId)"
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " UNIQUE (keySetId),"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_INCOMING_KEYS =
|
||||
"CREATE TABLE incomingKeys"
|
||||
+ " (transportId _STRING NOT NULL,"
|
||||
+ " keySetId INT NOT NULL,"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " transportId _STRING NOT NULL,"
|
||||
+ " rotationPeriod BIGINT NOT NULL,"
|
||||
+ " contactId INT," // Null if keys are not bound
|
||||
+ " tagKey _SECRET NOT NULL,"
|
||||
+ " headerKey _SECRET NOT NULL,"
|
||||
+ " base BIGINT NOT NULL,"
|
||||
+ " bitmap _BINARY NOT NULL,"
|
||||
+ " PRIMARY KEY (transportId, keySetId, rotationPeriod),"
|
||||
+ " FOREIGN KEY (transportId)"
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (keySetId)"
|
||||
+ " REFERENCES outgoingKeys (keySetId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " PRIMARY KEY (contactId, transportId, rotationPeriod),"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (transportId)"
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_OUTGOING_KEYS =
|
||||
"CREATE TABLE outgoingKeys"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " transportId _STRING NOT NULL,"
|
||||
+ " rotationPeriod BIGINT NOT NULL,"
|
||||
+ " tagKey _SECRET NOT NULL,"
|
||||
+ " headerKey _SECRET NOT NULL,"
|
||||
+ " stream BIGINT NOT NULL,"
|
||||
+ " PRIMARY KEY (contactId, transportId),"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (transportId)"
|
||||
+ " REFERENCES transports (transportId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String INDEX_CONTACTS_BY_AUTHOR_ID =
|
||||
@@ -277,10 +263,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||
+ " ON messageMetadata (groupId, state)";
|
||||
|
||||
private static final String INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID =
|
||||
"CREATE INDEX IF NOT EXISTS messageDependenciesByDependencyId"
|
||||
+ " ON messageDependencies (dependencyId)";
|
||||
|
||||
private static final String INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID =
|
||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdGroupId"
|
||||
+ " ON statuses (contactId, groupId)";
|
||||
@@ -319,8 +301,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);
|
||||
@@ -331,7 +312,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
Connection txn = startTransaction();
|
||||
try {
|
||||
if (reopen) {
|
||||
checkSchemaVersion(txn, listener);
|
||||
checkSchemaVersion(txn);
|
||||
} else {
|
||||
createTables(txn);
|
||||
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
|
||||
@@ -354,8 +335,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
* @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 void 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();
|
||||
@@ -368,7 +348,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
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
|
||||
@@ -424,8 +403,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
s.executeUpdate(insertTypeNames(CREATE_OFFERS));
|
||||
s.executeUpdate(insertTypeNames(CREATE_STATUSES));
|
||||
s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS));
|
||||
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
|
||||
s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
|
||||
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
@@ -440,7 +419,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||
s.executeUpdate(INDEX_MESSAGE_DEPENDENCIES_BY_DEPENDENCY_ID);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||
s.close();
|
||||
@@ -733,17 +711,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
m.getLength(), state, e.getValue(), messageShared,
|
||||
false, seen);
|
||||
}
|
||||
// Update denormalised column in messageDependencies if dependency
|
||||
// is in same group as dependent
|
||||
sql = "UPDATE messageDependencies SET dependencyState = ?"
|
||||
+ " WHERE groupId = ? AND dependencyId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, m.getGroupId().getBytes());
|
||||
ps.setBytes(3, m.getId().getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -813,42 +780,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMessageDependency(Connection txn, Message dependent,
|
||||
MessageId dependency, State dependentState) throws DbException {
|
||||
public void addMessageDependency(Connection txn, GroupId g,
|
||||
MessageId dependent, MessageId dependency) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Get state of dependency if present and in same group as dependent
|
||||
String sql = "SELECT state FROM messages"
|
||||
+ " WHERE messageId = ? AND groupId = ?";
|
||||
String sql = "INSERT INTO messageDependencies"
|
||||
+ " (groupId, messageId, dependencyId)"
|
||||
+ " VALUES (?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, dependency.getBytes());
|
||||
ps.setBytes(2, dependent.getGroupId().getBytes());
|
||||
rs = ps.executeQuery();
|
||||
State dependencyState = null;
|
||||
if (rs.next()) {
|
||||
dependencyState = State.fromValue(rs.getInt(1));
|
||||
if (rs.next()) throw new DbStateException();
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Create messageDependencies row
|
||||
sql = "INSERT INTO messageDependencies"
|
||||
+ " (groupId, messageId, dependencyId, messageState,"
|
||||
+ " dependencyState)"
|
||||
+ " VALUES (?, ?, ?, ? ,?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, dependent.getGroupId().getBytes());
|
||||
ps.setBytes(2, dependent.getId().getBytes());
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setBytes(2, dependent.getBytes());
|
||||
ps.setBytes(3, dependency.getBytes());
|
||||
ps.setInt(4, dependentState.getValue());
|
||||
if (dependencyState == null) ps.setNull(5, INTEGER);
|
||||
else ps.setInt(5, dependencyState.getValue());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
@@ -874,104 +820,60 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySetId addTransportKeys(Connection txn, @Nullable ContactId c,
|
||||
TransportKeys k) throws DbException {
|
||||
public void addTransportKeys(Connection txn, ContactId c, TransportKeys k)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Store the outgoing keys
|
||||
String sql = "INSERT INTO outgoingKeys (contactId, transportId,"
|
||||
+ " rotationPeriod, tagKey, headerKey, stream, active)"
|
||||
// Store the incoming keys
|
||||
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
|
||||
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
if (c == null) ps.setNull(1, INTEGER);
|
||||
else ps.setInt(1, c.getInt());
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setString(2, k.getTransportId().getString());
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
ps.setLong(3, outCurr.getRotationPeriod());
|
||||
ps.setBytes(4, outCurr.getTagKey().getBytes());
|
||||
ps.setBytes(5, outCurr.getHeaderKey().getBytes());
|
||||
ps.setLong(6, outCurr.getStreamCounter());
|
||||
ps.setBoolean(7, outCurr.isActive());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Get the new (highest) key set ID
|
||||
sql = "SELECT keySetId FROM outgoingKeys"
|
||||
+ " ORDER BY keySetId DESC LIMIT 1";
|
||||
ps = txn.prepareStatement(sql);
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
KeySetId keySetId = new KeySetId(rs.getInt(1));
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Store the incoming keys
|
||||
sql = "INSERT INTO incomingKeys (keySetId, contactId, transportId,"
|
||||
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, keySetId.getInt());
|
||||
if (c == null) ps.setNull(2, INTEGER);
|
||||
else ps.setInt(2, c.getInt());
|
||||
ps.setString(3, k.getTransportId().getString());
|
||||
// Previous rotation period
|
||||
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||
ps.setLong(4, inPrev.getRotationPeriod());
|
||||
ps.setBytes(5, inPrev.getTagKey().getBytes());
|
||||
ps.setBytes(6, inPrev.getHeaderKey().getBytes());
|
||||
ps.setLong(7, inPrev.getWindowBase());
|
||||
ps.setBytes(8, inPrev.getWindowBitmap());
|
||||
ps.setLong(3, inPrev.getRotationPeriod());
|
||||
ps.setBytes(4, inPrev.getTagKey().getBytes());
|
||||
ps.setBytes(5, inPrev.getHeaderKey().getBytes());
|
||||
ps.setLong(6, inPrev.getWindowBase());
|
||||
ps.setBytes(7, inPrev.getWindowBitmap());
|
||||
ps.addBatch();
|
||||
// Current rotation period
|
||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||
ps.setLong(4, inCurr.getRotationPeriod());
|
||||
ps.setBytes(5, inCurr.getTagKey().getBytes());
|
||||
ps.setBytes(6, inCurr.getHeaderKey().getBytes());
|
||||
ps.setLong(7, inCurr.getWindowBase());
|
||||
ps.setBytes(8, inCurr.getWindowBitmap());
|
||||
ps.setLong(3, inCurr.getRotationPeriod());
|
||||
ps.setBytes(4, inCurr.getTagKey().getBytes());
|
||||
ps.setBytes(5, inCurr.getHeaderKey().getBytes());
|
||||
ps.setLong(6, inCurr.getWindowBase());
|
||||
ps.setBytes(7, inCurr.getWindowBitmap());
|
||||
ps.addBatch();
|
||||
// Next rotation period
|
||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||
ps.setLong(4, inNext.getRotationPeriod());
|
||||
ps.setBytes(5, inNext.getTagKey().getBytes());
|
||||
ps.setBytes(6, inNext.getHeaderKey().getBytes());
|
||||
ps.setLong(7, inNext.getWindowBase());
|
||||
ps.setBytes(8, inNext.getWindowBitmap());
|
||||
ps.setLong(3, inNext.getRotationPeriod());
|
||||
ps.setBytes(4, inNext.getTagKey().getBytes());
|
||||
ps.setBytes(5, inNext.getHeaderKey().getBytes());
|
||||
ps.setLong(6, inNext.getWindowBase());
|
||||
ps.setBytes(7, inNext.getWindowBitmap());
|
||||
ps.addBatch();
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != 3) throw new DbStateException();
|
||||
for (int rows : batchAffected)
|
||||
if (rows != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
return keySetId;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTransportKeys(Connection txn, ContactId c, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE outgoingKeys SET contactId = ?"
|
||||
+ " WHERE keySetId = ?";
|
||||
// Store the outgoing keys
|
||||
sql = "INSERT INTO outgoingKeys (contactId, transportId,"
|
||||
+ " rotationPeriod, tagKey, headerKey, stream)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, k.getInt());
|
||||
ps.setString(2, k.getTransportId().getString());
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
ps.setLong(3, outCurr.getRotationPeriod());
|
||||
ps.setBytes(4, outCurr.getTagKey().getBytes());
|
||||
ps.setBytes(5, outCurr.getHeaderKey().getBytes());
|
||||
ps.setLong(6, outCurr.getStreamCounter());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
sql = "UPDATE incomingKeys SET contactId = ?"
|
||||
+ " WHERE keySetId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, k.getInt());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
@@ -1757,9 +1659,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT dependencyId, dependencyState"
|
||||
+ " FROM messageDependencies"
|
||||
+ " WHERE messageId = ?";
|
||||
String sql = "SELECT d.dependencyId, m.state, d.groupId, m.groupId"
|
||||
+ " FROM messageDependencies AS d"
|
||||
+ " LEFT OUTER JOIN messages AS m"
|
||||
+ " ON d.dependencyId = m.messageId"
|
||||
+ " WHERE d.messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
@@ -1767,8 +1671,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
while (rs.next()) {
|
||||
MessageId dependency = new MessageId(rs.getBytes(1));
|
||||
State state = State.fromValue(rs.getInt(2));
|
||||
if (rs.wasNull())
|
||||
state = UNKNOWN; // Missing or in a different group
|
||||
if (rs.wasNull()) {
|
||||
state = UNKNOWN; // Missing dependency
|
||||
} else {
|
||||
GroupId dependentGroupId = new GroupId(rs.getBytes(3));
|
||||
GroupId dependencyGroupId = new GroupId(rs.getBytes(4));
|
||||
if (!dependentGroupId.equals(dependencyGroupId))
|
||||
state = INVALID; // Dependency in another group
|
||||
}
|
||||
dependencies.put(dependency, state);
|
||||
}
|
||||
rs.close();
|
||||
@@ -1787,12 +1697,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Exclude dependencies that are missing or in a different group
|
||||
// from the dependent
|
||||
String sql = "SELECT messageId, messageState"
|
||||
+ " FROM messageDependencies"
|
||||
+ " WHERE dependencyId = ?"
|
||||
+ " AND dependencyState IS NOT NULL";
|
||||
String sql = "SELECT d.messageId, m.state"
|
||||
+ " FROM messageDependencies AS d"
|
||||
+ " JOIN messages AS m"
|
||||
+ " ON d.messageId = m.messageId"
|
||||
+ " WHERE dependencyId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
@@ -1955,26 +1864,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)));
|
||||
@@ -1989,7 +1900,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToShare(Connection txn)
|
||||
public Collection<MessageId> getMessagesToShare(Connection txn, ClientId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
@@ -1999,10 +1910,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)));
|
||||
@@ -2131,8 +2044,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<KeySet> getTransportKeys(Connection txn, TransportId t)
|
||||
throws DbException {
|
||||
public Map<ContactId, TransportKeys> getTransportKeys(Connection txn,
|
||||
TransportId t) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -2141,7 +2054,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " base, bitmap"
|
||||
+ " FROM incomingKeys"
|
||||
+ " WHERE transportId = ?"
|
||||
+ " ORDER BY keySetId, rotationPeriod";
|
||||
+ " ORDER BY contactId, rotationPeriod";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
@@ -2158,34 +2071,29 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
rs.close();
|
||||
ps.close();
|
||||
// Retrieve the outgoing keys in the same order
|
||||
sql = "SELECT keySetId, contactId, rotationPeriod,"
|
||||
+ " tagKey, headerKey, stream, active"
|
||||
sql = "SELECT contactId, rotationPeriod, tagKey, headerKey, stream"
|
||||
+ " FROM outgoingKeys"
|
||||
+ " WHERE transportId = ?"
|
||||
+ " ORDER BY keySetId";
|
||||
+ " ORDER BY contactId, rotationPeriod";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
rs = ps.executeQuery();
|
||||
Collection<KeySet> keys = new ArrayList<>();
|
||||
Map<ContactId, TransportKeys> keys = new HashMap<>();
|
||||
for (int i = 0; rs.next(); i++) {
|
||||
// There should be three times as many incoming keys
|
||||
if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
|
||||
KeySetId keySetId = new KeySetId(rs.getInt(1));
|
||||
ContactId contactId = new ContactId(rs.getInt(2));
|
||||
if (rs.wasNull()) contactId = null;
|
||||
long rotationPeriod = rs.getLong(3);
|
||||
SecretKey tagKey = new SecretKey(rs.getBytes(4));
|
||||
SecretKey headerKey = new SecretKey(rs.getBytes(5));
|
||||
long streamCounter = rs.getLong(6);
|
||||
boolean active = rs.getBoolean(7);
|
||||
ContactId contactId = new ContactId(rs.getInt(1));
|
||||
long rotationPeriod = rs.getLong(2);
|
||||
SecretKey tagKey = new SecretKey(rs.getBytes(3));
|
||||
SecretKey headerKey = new SecretKey(rs.getBytes(4));
|
||||
long streamCounter = rs.getLong(5);
|
||||
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
|
||||
rotationPeriod, streamCounter, active);
|
||||
rotationPeriod, streamCounter);
|
||||
IncomingKeys inPrev = inKeys.get(i * 3);
|
||||
IncomingKeys inCurr = inKeys.get(i * 3 + 1);
|
||||
IncomingKeys inNext = inKeys.get(i * 3 + 2);
|
||||
TransportKeys transportKeys = new TransportKeys(t, inPrev,
|
||||
inCurr, inNext, outCurr);
|
||||
keys.add(new KeySet(keySetId, contactId, transportKeys));
|
||||
keys.put(contactId, new TransportKeys(t, inPrev, inCurr,
|
||||
inNext, outCurr));
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
@@ -2198,15 +2106,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementStreamCounter(Connection txn, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
public void incrementStreamCounter(Connection txn, ContactId c,
|
||||
TransportId t, long rotationPeriod) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
||||
+ " WHERE transportId = ? AND keySetId = ?";
|
||||
+ " WHERE contactId = ? AND transportId = ?"
|
||||
+ " AND rotationPeriod = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
ps.setInt(2, k.getInt());
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setString(2, t.getString());
|
||||
ps.setLong(3, rotationPeriod);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -2682,27 +2592,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransportKeys(Connection txn, TransportId t, KeySetId k)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Delete any existing outgoing keys - this will also remove any
|
||||
// incoming keys with the same key set ID
|
||||
String sql = "DELETE FROM outgoingKeys"
|
||||
+ " WHERE transportId = ? AND keySetId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
ps.setInt(2, k.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetExpiryTime(Connection txn, ContactId c, MessageId m)
|
||||
throws DbException {
|
||||
@@ -2842,25 +2731,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in messageDependencies
|
||||
sql = "UPDATE messageDependencies SET messageState = ?"
|
||||
+ " 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 messageDependencies if dependency
|
||||
// is present and in same group as dependent
|
||||
sql = "UPDATE messageDependencies SET dependencyState = ?"
|
||||
+ " WHERE dependencyId = ? AND dependencyState IS NOT NULL";
|
||||
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);
|
||||
@@ -2868,18 +2738,18 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReorderingWindow(Connection txn, KeySetId k, TransportId t,
|
||||
public void setReorderingWindow(Connection txn, ContactId c, TransportId t,
|
||||
long rotationPeriod, long base, byte[] bitmap) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
||||
+ " WHERE transportId = ? AND keySetId = ?"
|
||||
+ " WHERE contactId = ? AND transportId = ?"
|
||||
+ " AND rotationPeriod = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setLong(1, base);
|
||||
ps.setBytes(2, bitmap);
|
||||
ps.setString(3, t.getString());
|
||||
ps.setInt(4, k.getInt());
|
||||
ps.setInt(3, c.getInt());
|
||||
ps.setString(4, t.getString());
|
||||
ps.setLong(5, rotationPeriod);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
@@ -2890,25 +2760,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransportKeysActive(Connection txn, TransportId t,
|
||||
KeySetId k) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "UPDATE outgoingKeys SET active = true"
|
||||
+ " WHERE transportId = ? AND keySetId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, t.getString());
|
||||
ps.setInt(2, k.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateExpiryTime(Connection txn, ContactId c, MessageId m,
|
||||
int maxLatency) throws DbException {
|
||||
@@ -2944,12 +2795,45 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTransportKeys(Connection txn, Collection<KeySet> keys)
|
||||
throws DbException {
|
||||
for (KeySet ks : keys) {
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
removeTransportKeys(txn, k.getTransportId(), ks.getKeySetId());
|
||||
addTransportKeys(txn, ks.getContactId(), k);
|
||||
public void updateTransportKeys(Connection txn,
|
||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
// Delete any existing incoming keys
|
||||
String sql = "DELETE FROM incomingKeys"
|
||||
+ " WHERE contactId = ?"
|
||||
+ " AND transportId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||
ps.setInt(1, e.getKey().getInt());
|
||||
ps.setString(2, e.getValue().getTransportId().getString());
|
||||
ps.addBatch();
|
||||
}
|
||||
int[] batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != keys.size())
|
||||
throw new DbStateException();
|
||||
ps.close();
|
||||
// Delete any existing outgoing keys
|
||||
sql = "DELETE FROM outgoingKeys"
|
||||
+ " WHERE contactId = ?"
|
||||
+ " AND transportId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||
ps.setInt(1, e.getKey().getInt());
|
||||
ps.setString(2, e.getValue().getTransportId().getString());
|
||||
ps.addBatch();
|
||||
}
|
||||
batchAffected = ps.executeBatch();
|
||||
if (batchAffected.length != keys.size())
|
||||
throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
// Store the new keys
|
||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||
addTransportKeys(txn, e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,35 +2,38 @@ 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.
|
||||
* Adds a connection to the set of connections that may be chosen. If
|
||||
* {@link #stop()} has already been called, the connection will be closed
|
||||
* immediately.
|
||||
*/
|
||||
void submit(Callable<KeyAgreementConnection> task);
|
||||
void addConnection(KeyAgreementConnection c);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Chooses one of the connections passed to
|
||||
* {@link #addConnection(KeyAgreementConnection)} and returns it,
|
||||
* waiting up to the given amount of time for a suitable connection to
|
||||
* become available. Returns null if the time elapses without a suitable
|
||||
* connection becoming available.
|
||||
*
|
||||
* @param alice true if the local party is Alice
|
||||
* @param timeout the timeout in milliseconds
|
||||
* @throws InterruptedException if the thread is interrupted while waiting
|
||||
* for a connection to become available
|
||||
* for a suitable connection to become available
|
||||
*/
|
||||
@Nullable
|
||||
KeyAgreementConnection poll(long timeout) throws InterruptedException;
|
||||
KeyAgreementConnection chooseConnection(boolean alice, 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.
|
||||
* Stops the chooser from accepting new connections. Closes any connections
|
||||
* already passed to {@link #addConnection(KeyAgreementConnection)}
|
||||
* and not chosen. Any connections subsequently passed to
|
||||
* {@link #addConnection(KeyAgreementConnection)} will be closed.
|
||||
*/
|
||||
void stop();
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||
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.Iterator;
|
||||
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;
|
||||
@@ -20,6 +18,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@NotNullByDefault
|
||||
@ThreadSafe
|
||||
@@ -28,54 +27,114 @@ class ConnectionChooserImpl implements ConnectionChooser {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectionChooserImpl.class.getName());
|
||||
|
||||
private final EventBus eventBus;
|
||||
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<>();
|
||||
private final List<KeyAgreementConnection> connections =
|
||||
new ArrayList<>(); // Locking: lock
|
||||
private boolean stopped = false; // Locking: lock
|
||||
|
||||
@Inject
|
||||
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
|
||||
ConnectionChooserImpl(EventBus eventBus, Clock clock) {
|
||||
this.eventBus = eventBus;
|
||||
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());
|
||||
public void addConnection(KeyAgreementConnection conn) {
|
||||
boolean close = false;
|
||||
synchronized (lock) {
|
||||
if (stopped) {
|
||||
// Already stopped, close the connection
|
||||
close = true;
|
||||
} else {
|
||||
connections.add(conn);
|
||||
lock.notifyAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (close) tryToClose(conn.getConnection());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection poll(long timeout)
|
||||
public KeyAgreementConnection chooseConnection(boolean alice, long timeout)
|
||||
throws InterruptedException {
|
||||
if (alice) return chooseConnectionAlice(timeout);
|
||||
else return chooseConnectionBob(timeout);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private KeyAgreementConnection chooseConnectionAlice(long timeout)
|
||||
throws InterruptedException {
|
||||
LOG.info("Choosing connection for Alice");
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + timeout;
|
||||
KeyAgreementConnection chosen;
|
||||
synchronized (lock) {
|
||||
while (!stopped && results.isEmpty() && now < end) {
|
||||
// Wait until we're stopped, a connection is added, or we time out
|
||||
while (!stopped && connections.isEmpty() && now < end) {
|
||||
lock.wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
return results.poll();
|
||||
if (connections.isEmpty()) {
|
||||
LOG.info("No suitable connection for Alice");
|
||||
return null;
|
||||
}
|
||||
// Choose the first connection
|
||||
chosen = connections.remove(0);
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Choosing " + chosen.getTransportId());
|
||||
return chosen;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private KeyAgreementConnection chooseConnectionBob(long timeout)
|
||||
throws InterruptedException {
|
||||
LOG.info("Choosing connection for Bob");
|
||||
// Bob waits here for Alice to scan his QR code, determine her role,
|
||||
// choose a connection and send her key
|
||||
eventBus.broadcast(new KeyAgreementWaitingEvent());
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + timeout;
|
||||
synchronized (lock) {
|
||||
while (!stopped && now < end) {
|
||||
// Check whether any connection has data available
|
||||
Iterator<KeyAgreementConnection> it = connections.iterator();
|
||||
while (it.hasNext()) {
|
||||
KeyAgreementConnection c = it.next();
|
||||
try {
|
||||
int available = c.getConnection().getReader()
|
||||
.getInputStream().available();
|
||||
if (available > 0) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Choosing " + c.getTransportId());
|
||||
it.remove();
|
||||
return c;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
tryToClose(c.getConnection());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
// Wait for 1 second before checking again
|
||||
lock.wait(Math.min(1000, end - now));
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
LOG.info("No suitable connection for Bob");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
List<KeyAgreementConnection> unused;
|
||||
synchronized (lock) {
|
||||
unused = new ArrayList<>(results);
|
||||
results.clear();
|
||||
stopped = true;
|
||||
unused = new ArrayList<>(connections);
|
||||
connections.clear();
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -83,24 +142,6 @@ class ConnectionChooserImpl implements ConnectionChooser {
|
||||
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);
|
||||
|
||||
@@ -7,21 +7,20 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Plugin;
|
||||
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.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -33,31 +32,27 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CO
|
||||
@NotNullByDefault
|
||||
class KeyAgreementConnector {
|
||||
|
||||
interface Callbacks {
|
||||
void connectionWaiting();
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||
|
||||
private final Callbacks callbacks;
|
||||
private final Clock clock;
|
||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||
private final PluginManager pluginManager;
|
||||
private final Executor ioExecutor;
|
||||
private final ConnectionChooser connectionChooser;
|
||||
|
||||
private final List<KeyAgreementListener> listeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final CountDownLatch aliceLatch = new CountDownLatch(1);
|
||||
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean alice = false, stopped = false;
|
||||
private volatile boolean stopped = false;
|
||||
|
||||
KeyAgreementConnector(Callbacks callbacks,
|
||||
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
|
||||
KeyAgreementConnector(Clock clock, KeyAgreementCrypto keyAgreementCrypto,
|
||||
PluginManager pluginManager, Executor ioExecutor,
|
||||
ConnectionChooser connectionChooser) {
|
||||
this.callbacks = callbacks;
|
||||
this.clock = clock;
|
||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||
this.pluginManager = pluginManager;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.connectionChooser = connectionChooser;
|
||||
}
|
||||
|
||||
@@ -76,7 +71,14 @@ class KeyAgreementConnector {
|
||||
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
|
||||
listeners.add(l);
|
||||
connectionChooser.submit(new ReadableTask(l::accept));
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
connectionChooser.addConnection(l.accept());
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return new Payload(commitment, descriptors);
|
||||
@@ -85,17 +87,13 @@ class KeyAgreementConnector {
|
||||
void stopListening() {
|
||||
LOG.info("Stopping BQP listeners");
|
||||
stopped = true;
|
||||
aliceLatch.countDown();
|
||||
for (KeyAgreementListener l : listeners) l.close();
|
||||
listeners.clear();
|
||||
connectionChooser.stop();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
|
||||
// Let the ReadableTasks know if we are Alice
|
||||
this.alice = alice;
|
||||
aliceLatch.countDown();
|
||||
|
||||
// Start connecting over supported transports
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Starting outgoing BQP connections as "
|
||||
@@ -109,16 +107,23 @@ class KeyAgreementConnector {
|
||||
DuplexPlugin plugin = (DuplexPlugin) p;
|
||||
byte[] commitment = remotePayload.getCommitment();
|
||||
BdfList descriptor = d.getDescriptor();
|
||||
connectionChooser.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, commitment, descriptor)));
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
KeyAgreementConnection c =
|
||||
connect(plugin, commitment, descriptor);
|
||||
if (c != null) connectionChooser.addConnection(c);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting to connect");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get chosen connection
|
||||
try {
|
||||
KeyAgreementConnection chosen =
|
||||
connectionChooser.poll(CONNECTION_TIMEOUT);
|
||||
if (chosen == null) return null;
|
||||
KeyAgreementConnection chosen = connectionChooser.chooseConnection(
|
||||
alice, CONNECTION_TIMEOUT);
|
||||
if (chosen == null) return null; // No suitable connection
|
||||
return new KeyAgreementTransport(chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
@@ -132,70 +137,20 @@ class KeyAgreementConnector {
|
||||
}
|
||||
}
|
||||
|
||||
private void waitingForAlice() {
|
||||
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
|
||||
}
|
||||
|
||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final byte[] commitment;
|
||||
private final BdfList descriptor;
|
||||
private final DuplexPlugin plugin;
|
||||
|
||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
||||
BdfList descriptor) {
|
||||
this.plugin = plugin;
|
||||
this.commitment = commitment;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
// Repeat attempts until we connect, get stopped, or get interrupted
|
||||
while (!stopped) {
|
||||
DuplexTransportConnection conn =
|
||||
plugin.createKeyAgreementConnection(commitment,
|
||||
descriptor);
|
||||
if (conn != null) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(plugin.getId() + ": 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 final Callable<KeyAgreementConnection> connectionTask;
|
||||
|
||||
private ReadableTask(Callable<KeyAgreementConnection> connectionTask) {
|
||||
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);
|
||||
}
|
||||
if (!stopped && LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId().getString() + ": Data available");
|
||||
return c;
|
||||
@Nullable
|
||||
@IoExecutor
|
||||
private KeyAgreementConnection connect(DuplexPlugin plugin,
|
||||
byte[] commitment, BdfList descriptor) throws InterruptedException {
|
||||
// Repeat attempts until we time out, get stopped, or get interrupted
|
||||
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||
while (!stopped && clock.currentTimeMillis() < end) {
|
||||
DuplexTransportConnection conn =
|
||||
plugin.createKeyAgreementConnection(commitment, descriptor);
|
||||
if (conn != null)
|
||||
return new KeyAgreementConnection(conn, plugin.getId());
|
||||
// Wait 2s before retry (to circumvent transient failures)
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,14 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
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 javax.inject.Inject;
|
||||
@@ -28,8 +31,8 @@ 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, KeyAgreementProtocol.Callbacks {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||
@@ -45,17 +48,18 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
private Payload remotePayload;
|
||||
|
||||
@Inject
|
||||
KeyAgreementTaskImpl(CryptoComponent crypto,
|
||||
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
||||
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
ConnectionChooser connectionChooser) {
|
||||
this.crypto = crypto;
|
||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||
this.eventBus = eventBus;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
localKeyPair = crypto.generateAgreementKeyPair();
|
||||
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
|
||||
pluginManager, connectionChooser);
|
||||
connector = new KeyAgreementConnector(clock, keyAgreementCrypto,
|
||||
pluginManager, ioExecutor, connectionChooser);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,12 +247,12 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
ctx = keyManager.getStreamContext(transportId, tag);
|
||||
} catch (IOException | DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
dispose(true, false);
|
||||
return;
|
||||
}
|
||||
if (ctx == null) {
|
||||
LOG.info("Unrecognised tag");
|
||||
disposeReader(false, false);
|
||||
dispose(false, false);
|
||||
return;
|
||||
}
|
||||
contactId = ctx.getContactId();
|
||||
@@ -263,10 +263,10 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
// Create and run the incoming session
|
||||
incomingSession = createIncomingSession(ctx, reader);
|
||||
incomingSession.run();
|
||||
disposeReader(false, true);
|
||||
dispose(false, true);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, true);
|
||||
dispose(true, true);
|
||||
} finally {
|
||||
connectionRegistry.unregisterConnection(contactId, transportId,
|
||||
true);
|
||||
@@ -280,39 +280,28 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
ctx = keyManager.getStreamContext(contactId, transportId);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeWriter(true);
|
||||
dispose(true, true);
|
||||
return;
|
||||
}
|
||||
if (ctx == null) {
|
||||
LOG.warning("Could not allocate stream context");
|
||||
disposeWriter(true);
|
||||
dispose(true, true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Create and run the outgoing session
|
||||
outgoingSession = createDuplexOutgoingSession(ctx, writer);
|
||||
outgoingSession.run();
|
||||
disposeWriter(false);
|
||||
dispose(false, true);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeWriter(true);
|
||||
dispose(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void disposeReader(boolean exception, boolean recognised) {
|
||||
if (exception && outgoingSession != null)
|
||||
outgoingSession.interrupt();
|
||||
private void dispose(boolean exception, boolean recognised) {
|
||||
try {
|
||||
reader.dispose(exception, recognised);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void disposeWriter(boolean exception) {
|
||||
if (exception && incomingSession != null)
|
||||
incomingSession.interrupt();
|
||||
try {
|
||||
writer.dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
@@ -346,12 +335,12 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
ctx = keyManager.getStreamContext(contactId, transportId);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeWriter(true);
|
||||
dispose(true);
|
||||
return;
|
||||
}
|
||||
if (ctx == null) {
|
||||
LOG.warning("Could not allocate stream context");
|
||||
disposeWriter(true);
|
||||
dispose(true);
|
||||
return;
|
||||
}
|
||||
// Start the incoming session on another thread
|
||||
@@ -360,10 +349,10 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
// Create and run the outgoing session
|
||||
outgoingSession = createDuplexOutgoingSession(ctx, writer);
|
||||
outgoingSession.run();
|
||||
disposeWriter(false);
|
||||
dispose(false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeWriter(true);
|
||||
dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,19 +364,19 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
ctx = keyManager.getStreamContext(transportId, tag);
|
||||
} catch (IOException | DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, false);
|
||||
dispose(true);
|
||||
return;
|
||||
}
|
||||
// Unrecognised tags are suspicious in this case
|
||||
if (ctx == null) {
|
||||
LOG.warning("Unrecognised tag for returning stream");
|
||||
disposeReader(true, false);
|
||||
dispose(true);
|
||||
return;
|
||||
}
|
||||
// Check that the stream comes from the expected contact
|
||||
if (!ctx.getContactId().equals(contactId)) {
|
||||
LOG.warning("Wrong contact ID for returning stream");
|
||||
disposeReader(true, true);
|
||||
dispose(true);
|
||||
return;
|
||||
}
|
||||
connectionRegistry.registerConnection(contactId, transportId,
|
||||
@@ -396,30 +385,20 @@ class ConnectionManagerImpl implements ConnectionManager {
|
||||
// Create and run the incoming session
|
||||
incomingSession = createIncomingSession(ctx, reader);
|
||||
incomingSession.run();
|
||||
disposeReader(false, true);
|
||||
dispose(false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
disposeReader(true, true);
|
||||
dispose(true);
|
||||
} finally {
|
||||
connectionRegistry.unregisterConnection(contactId, transportId,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
private void disposeReader(boolean exception, boolean recognised) {
|
||||
if (exception && outgoingSession != null)
|
||||
outgoingSession.interrupt();
|
||||
try {
|
||||
reader.dispose(exception, recognised);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void disposeWriter(boolean exception) {
|
||||
if (exception && incomingSession != null)
|
||||
incomingSession.interrupt();
|
||||
private void dispose(boolean exception) {
|
||||
try {
|
||||
// 'Recognised' is always true because we opened the connection
|
||||
reader.dispose(exception, true);
|
||||
writer.dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
@NotNullByDefault
|
||||
interface BluetoothConnectionManager {
|
||||
|
||||
/**
|
||||
* Returns true if a contact connection can be opened without exceeding
|
||||
* the connection limit. This method does not need to be called for key
|
||||
* exchange connections.
|
||||
*/
|
||||
boolean canOpenConnection();
|
||||
|
||||
/**
|
||||
* Passes a newly opened connection to the manager. The manager may close
|
||||
* the new connection or another connection to stay within the connection
|
||||
* limit.
|
||||
* <p/>
|
||||
* Returns false if the manager has closed the new connection (this will
|
||||
* never be the case for key exchange connections).
|
||||
*/
|
||||
boolean connectionOpened(DuplexTransportConnection conn,
|
||||
boolean isForKeyExchange);
|
||||
|
||||
/**
|
||||
* Informs the manager that the given connection has been closed.
|
||||
*/
|
||||
void connectionClosed(DuplexTransportConnection conn);
|
||||
|
||||
/**
|
||||
* Informs the manager that all connections have been closed.
|
||||
*/
|
||||
void allConnectionsClosed();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@NotNullByDefault
|
||||
@ThreadSafe
|
||||
class BluetoothConnectionManagerImpl implements BluetoothConnectionManager {
|
||||
|
||||
private static final int MAX_OPEN_CONNECTIONS = 5;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothConnectionManagerImpl.class.getName());
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final LinkedList<DuplexTransportConnection> connections =
|
||||
new LinkedList<>(); // Locking: lock
|
||||
|
||||
@Override
|
||||
public boolean canOpenConnection() {
|
||||
synchronized (lock) {
|
||||
int open = connections.size();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(open + " open connections");
|
||||
return open < MAX_OPEN_CONNECTIONS;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectionOpened(DuplexTransportConnection conn,
|
||||
boolean isForKeyExchange) {
|
||||
DuplexTransportConnection close = null;
|
||||
synchronized (lock) {
|
||||
int open = connections.size();
|
||||
boolean accept = isForKeyExchange || open < MAX_OPEN_CONNECTIONS;
|
||||
if (accept) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Accepting connection, " + (open + 1) + " open");
|
||||
connections.add(conn);
|
||||
if (open == MAX_OPEN_CONNECTIONS) {
|
||||
LOG.info("Closing old connection to stay within limit");
|
||||
close = connections.poll();
|
||||
}
|
||||
} else {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Refusing connection, " + open + " open");
|
||||
close = conn;
|
||||
}
|
||||
}
|
||||
if (close != null) tryToClose(close);
|
||||
return close != conn;
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
try {
|
||||
conn.getReader().dispose(false, true);
|
||||
conn.getWriter().dispose(false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionClosed(DuplexTransportConnection conn) {
|
||||
synchronized (lock) {
|
||||
connections.remove(conn);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
int open = connections.size();
|
||||
LOG.info("Connection closed, " + open + " open");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allConnectionsClosed() {
|
||||
synchronized (lock) {
|
||||
connections.clear();
|
||||
LOG.info("All connections closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
|
||||
protected final BluetoothConnectionManager connectionManager;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
@@ -91,8 +93,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException;
|
||||
|
||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
BluetoothPlugin(BluetoothConnectionManager connectionManager,
|
||||
Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
this.connectionManager = connectionManager;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
@@ -110,6 +114,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
void onAdapterDisabled() {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
connectionManager.allConnectionsClosed();
|
||||
callback.transportDisabled();
|
||||
}
|
||||
|
||||
@@ -213,7 +218,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
return;
|
||||
}
|
||||
backoff.reset();
|
||||
callback.incomingConnectionCreated(conn);
|
||||
if (connectionManager.connectionOpened(conn, false))
|
||||
callback.incomingConnectionCreated(conn);
|
||||
if (!running) return;
|
||||
}
|
||||
}
|
||||
@@ -257,10 +263,15 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
if (!connectionManager.canOpenConnection()) {
|
||||
LOG.info("Not connecting, too many open connections");
|
||||
return;
|
||||
}
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
if (connectionManager.connectionOpened(conn, false))
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -300,12 +311,19 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||
if (!connectionManager.canOpenConnection()) {
|
||||
LOG.info("Not connecting, too many open connections");
|
||||
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);
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn == null) return null;
|
||||
// TODO: Why don't we reset the backoff here?
|
||||
return connectionManager.connectionOpened(conn, false) ? conn : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -355,7 +373,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);
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
// The connection limit doesn't apply to key agreement
|
||||
if (conn != null) connectionManager.connectionOpened(conn, true);
|
||||
return conn;
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
@@ -408,6 +429,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
DuplexTransportConnection conn = acceptConnection(ss);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
// The connection limit doesn't apply to key agreement
|
||||
connectionManager.connectionOpened(conn, true);
|
||||
return new KeyAgreementConnection(conn, ID);
|
||||
}
|
||||
|
||||
|
||||
@@ -241,11 +241,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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -46,7 +46,8 @@ public class PropertiesModule {
|
||||
lifecycleManager.registerClient(transportPropertyManager);
|
||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
||||
transportPropertyManager);
|
||||
contactManager.registerContactHook(transportPropertyManager);
|
||||
contactManager.registerAddContactHook(transportPropertyManager);
|
||||
contactManager.registerRemoveContactHook(transportPropertyManager);
|
||||
return transportPropertyManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.AddContactHook;
|
||||
import org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
@@ -39,7 +40,7 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
Client, ContactHook, IncomingMessageHook {
|
||||
Client, AddContactHook, RemoveContactHook, IncomingMessageHook {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
@@ -347,7 +348,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||
throws FormatException {
|
||||
// Transport ID, version, properties
|
||||
BdfDictionary dictionary = message.getDictionary(2);
|
||||
return clientHelper.parseAndValidateTransportProperties(dictionary);
|
||||
TransportProperties p = new TransportProperties();
|
||||
for (String key : dictionary.keySet())
|
||||
p.put(key, dictionary.getString(key));
|
||||
return p;
|
||||
}
|
||||
|
||||
private static class LatestUpdate {
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.system.Clock;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
|
||||
@@ -40,7 +42,12 @@ class TransportPropertyValidator extends BdfMessageValidator {
|
||||
if (version < 0) throw new FormatException();
|
||||
// Properties
|
||||
BdfDictionary dictionary = body.getDictionary(2);
|
||||
clientHelper.parseAndValidateTransportProperties(dictionary);
|
||||
checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
|
||||
for (String key : dictionary.keySet()) {
|
||||
checkLength(key, 0, MAX_PROPERTY_LENGTH);
|
||||
String value = dictionary.getString(key);
|
||||
checkLength(value, 0, MAX_PROPERTY_LENGTH);
|
||||
}
|
||||
// Return the metadata
|
||||
BdfDictionary meta = new BdfDictionary();
|
||||
meta.put("transportId", transportId);
|
||||
|
||||
@@ -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;
|
||||
@@ -38,7 +38,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;
|
||||
|
||||
@@ -210,9 +209,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
} 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();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.Group.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.GroupId.LABEL;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||
|
||||
@Immutable
|
||||
@@ -31,7 +31,7 @@ class GroupFactoryImpl implements GroupFactory {
|
||||
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
||||
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
||||
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
||||
byte[] hash = crypto.hash(LABEL, new byte[] {FORMAT_VERSION},
|
||||
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
||||
StringUtils.toUtf8(c.getString()), clientVersionBytes,
|
||||
descriptor);
|
||||
return new Group(new GroupId(hash), c, descriptor);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@ import org.briarproject.bramble.util.ByteUtils;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.Message.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.MessageId.BLOCK_LABEL;
|
||||
import static org.briarproject.bramble.api.sync.MessageId.ID_LABEL;
|
||||
import static org.briarproject.bramble.api.sync.MessageId.LABEL;
|
||||
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.ByteUtils.INT_64_BYTES;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -34,14 +32,11 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
public Message createMessage(GroupId g, long timestamp, byte[] body) {
|
||||
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] versionBytes = new byte[] {FORMAT_VERSION};
|
||||
// There's only one block, so the root hash is the hash of the block
|
||||
byte[] rootHash = crypto.hash(BLOCK_LABEL, versionBytes, body);
|
||||
byte[] timeBytes = new byte[INT_64_BYTES];
|
||||
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||
byte[] idHash = crypto.hash(ID_LABEL, versionBytes, g.getBytes(),
|
||||
timeBytes, rootHash);
|
||||
MessageId id = new MessageId(idHash);
|
||||
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
||||
g.getBytes(), timeBytes, body);
|
||||
MessageId id = new MessageId(hash);
|
||||
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
||||
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
@@ -28,7 +28,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +19,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -105,67 +104,6 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
m.addContact(txn, c, master, timestamp, alice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, KeySetId> addUnboundKeys(Transaction txn,
|
||||
SecretKey master, long timestamp, boolean alice)
|
||||
throws DbException {
|
||||
Map<TransportId, KeySetId> ids = new HashMap<>();
|
||||
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
TransportKeyManager m = e.getValue();
|
||||
ids.put(t, m.addUnboundKeys(txn, master, timestamp, alice));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindKeys(Transaction txn, ContactId c,
|
||||
Map<TransportId, KeySetId> keys) throws DbException {
|
||||
for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
} else {
|
||||
m.bindKeys(txn, c, e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
throws DbException {
|
||||
for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
} else {
|
||||
m.activateKeys(txn, e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeKeys(Transaction txn, Map<TransportId, KeySetId> keys)
|
||||
throws DbException {
|
||||
for (Entry<TransportId, KeySetId> e : keys.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
TransportKeyManager m = managers.get(t);
|
||||
if (m == null) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
} else {
|
||||
m.removeKeys(txn, e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSendOutgoingStreams(ContactId c, TransportId t) {
|
||||
TransportKeyManager m = managers.get(t);
|
||||
return m == null ? false : m.canSendOutgoingStreams(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamContext getStreamContext(ContactId c, TransportId t)
|
||||
throws DbException {
|
||||
@@ -176,7 +114,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
return null;
|
||||
}
|
||||
StreamContext ctx;
|
||||
StreamContext ctx = null;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
ctx = m.getStreamContext(txn, c);
|
||||
@@ -195,7 +133,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||
return null;
|
||||
}
|
||||
StreamContext ctx;
|
||||
StreamContext ctx = null;
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
ctx = m.getStreamContext(txn, tag);
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.briarproject.bramble.transport;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class MutableKeySet {
|
||||
|
||||
private final KeySetId keySetId;
|
||||
@Nullable
|
||||
private final ContactId contactId;
|
||||
private final MutableTransportKeys transportKeys;
|
||||
|
||||
public MutableKeySet(KeySetId keySetId, @Nullable ContactId contactId,
|
||||
MutableTransportKeys transportKeys) {
|
||||
this.keySetId = keySetId;
|
||||
this.contactId = contactId;
|
||||
this.transportKeys = transportKeys;
|
||||
}
|
||||
|
||||
public KeySetId getKeySetId() {
|
||||
return keySetId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public MutableTransportKeys getTransportKeys() {
|
||||
return transportKeys;
|
||||
}
|
||||
}
|
||||
@@ -13,19 +13,17 @@ class MutableOutgoingKeys {
|
||||
private final SecretKey tagKey, headerKey;
|
||||
private final long rotationPeriod;
|
||||
private long streamCounter;
|
||||
private boolean active;
|
||||
|
||||
MutableOutgoingKeys(OutgoingKeys out) {
|
||||
tagKey = out.getTagKey();
|
||||
headerKey = out.getHeaderKey();
|
||||
rotationPeriod = out.getRotationPeriod();
|
||||
streamCounter = out.getStreamCounter();
|
||||
active = out.isActive();
|
||||
}
|
||||
|
||||
OutgoingKeys snapshot() {
|
||||
return new OutgoingKeys(tagKey, headerKey, rotationPeriod,
|
||||
streamCounter, active);
|
||||
streamCounter);
|
||||
}
|
||||
|
||||
SecretKey getTagKey() {
|
||||
@@ -47,12 +45,4 @@ class MutableOutgoingKeys {
|
||||
void incrementStreamCounter() {
|
||||
streamCounter++;
|
||||
}
|
||||
|
||||
boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
void activate() {
|
||||
active = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -18,19 +17,8 @@ interface TransportKeyManager {
|
||||
void addContact(Transaction txn, ContactId c, SecretKey master,
|
||||
long timestamp, boolean alice) throws DbException;
|
||||
|
||||
KeySetId addUnboundKeys(Transaction txn, SecretKey master, long timestamp,
|
||||
boolean alice) throws DbException;
|
||||
|
||||
void bindKeys(Transaction txn, ContactId c, KeySetId k) throws DbException;
|
||||
|
||||
void activateKeys(Transaction txn, KeySetId k) throws DbException;
|
||||
|
||||
void removeKeys(Transaction txn, KeySetId k) throws DbException;
|
||||
|
||||
void removeContact(ContactId c);
|
||||
|
||||
boolean canSendOutgoingStreams(ContactId c);
|
||||
|
||||
@Nullable
|
||||
StreamContext getStreamContext(Transaction txn, ContactId c)
|
||||
throws DbException;
|
||||
|
||||
@@ -11,24 +11,19 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.transport.ReorderingWindow.Change;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
@@ -52,13 +47,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
private final Clock clock;
|
||||
private final TransportId transportId;
|
||||
private final long rotationPeriodLength;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final ReentrantLock lock;
|
||||
|
||||
// The following are locking: lock
|
||||
private final Map<KeySetId, MutableKeySet> keys = new HashMap<>();
|
||||
private final Map<Bytes, TagContext> inContexts = new HashMap<>();
|
||||
private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>();
|
||||
private final Map<Bytes, TagContext> inContexts;
|
||||
private final Map<ContactId, MutableOutgoingKeys> outContexts;
|
||||
private final Map<ContactId, MutableTransportKeys> keys;
|
||||
|
||||
TransportKeyManagerImpl(DatabaseComponent db,
|
||||
TransportCrypto transportCrypto, Executor dbExecutor,
|
||||
@@ -71,16 +65,20 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
this.clock = clock;
|
||||
this.transportId = transportId;
|
||||
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||
lock = new ReentrantLock();
|
||||
inContexts = new HashMap<>();
|
||||
outContexts = new HashMap<>();
|
||||
keys = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Transaction txn) throws DbException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
long now = clock.currentTimeMillis();
|
||||
lock.lock();
|
||||
try {
|
||||
// Load the transport keys from the DB
|
||||
Collection<KeySet> loaded = db.getTransportKeys(txn, transportId);
|
||||
Map<ContactId, TransportKeys> loaded =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
// Rotate the keys to the current rotation period
|
||||
RotationResult rotationResult = rotateKeys(loaded, now);
|
||||
// Initialise mutable state for all contacts
|
||||
@@ -95,48 +93,41 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
scheduleKeyRotation(now);
|
||||
}
|
||||
|
||||
private RotationResult rotateKeys(Collection<KeySet> keys, long now) {
|
||||
private RotationResult rotateKeys(Map<ContactId, TransportKeys> keys,
|
||||
long now) {
|
||||
RotationResult rotationResult = new RotationResult();
|
||||
long rotationPeriod = now / rotationPeriodLength;
|
||||
for (KeySet ks : keys) {
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
TransportKeys k = e.getValue();
|
||||
TransportKeys k1 =
|
||||
transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||
KeySet ks1 = new KeySet(ks.getKeySetId(), ks.getContactId(), k1);
|
||||
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
||||
rotationResult.rotated.add(ks1);
|
||||
rotationResult.current.add(ks1);
|
||||
rotationResult.rotated.put(c, k1);
|
||||
rotationResult.current.put(c, k1);
|
||||
}
|
||||
return rotationResult;
|
||||
}
|
||||
|
||||
// Locking: lock
|
||||
private void addKeys(Collection<KeySet> keys) {
|
||||
for (KeySet ks : keys) {
|
||||
addKeys(ks.getKeySetId(), ks.getContactId(),
|
||||
new MutableTransportKeys(ks.getTransportKeys()));
|
||||
}
|
||||
private void addKeys(Map<ContactId, TransportKeys> m) {
|
||||
for (Entry<ContactId, TransportKeys> e : m.entrySet())
|
||||
addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
|
||||
}
|
||||
|
||||
// Locking: lock
|
||||
private void addKeys(KeySetId keySetId, @Nullable ContactId contactId,
|
||||
MutableTransportKeys m) {
|
||||
MutableKeySet ks = new MutableKeySet(keySetId, contactId, m);
|
||||
keys.put(keySetId, ks);
|
||||
if (contactId != null) {
|
||||
encodeTags(keySetId, contactId, m.getPreviousIncomingKeys());
|
||||
encodeTags(keySetId, contactId, m.getCurrentIncomingKeys());
|
||||
encodeTags(keySetId, contactId, m.getNextIncomingKeys());
|
||||
considerReplacingOutgoingKeys(ks);
|
||||
}
|
||||
private void addKeys(ContactId c, MutableTransportKeys m) {
|
||||
encodeTags(c, m.getPreviousIncomingKeys());
|
||||
encodeTags(c, m.getCurrentIncomingKeys());
|
||||
encodeTags(c, m.getNextIncomingKeys());
|
||||
outContexts.put(c, m.getCurrentOutgoingKeys());
|
||||
keys.put(c, m);
|
||||
}
|
||||
|
||||
// Locking: lock
|
||||
private void encodeTags(KeySetId keySetId, ContactId contactId,
|
||||
MutableIncomingKeys inKeys) {
|
||||
private void encodeTags(ContactId c, MutableIncomingKeys inKeys) {
|
||||
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
||||
TagContext tagCtx =
|
||||
new TagContext(keySetId, contactId, inKeys, streamNumber);
|
||||
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
||||
streamNumber);
|
||||
@@ -144,17 +135,6 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Locking: lock
|
||||
private void considerReplacingOutgoingKeys(MutableKeySet ks) {
|
||||
// Use the active outgoing keys with the highest key set ID
|
||||
if (ks.getTransportKeys().getCurrentOutgoingKeys().isActive()) {
|
||||
MutableKeySet old = outContexts.get(ks.getContactId());
|
||||
if (old == null ||
|
||||
old.getKeySetId().getInt() < ks.getKeySetId().getInt())
|
||||
outContexts.put(ks.getContactId(), ks);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleKeyRotation(long now) {
|
||||
long delay = rotationPeriodLength - now % rotationPeriodLength;
|
||||
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS);
|
||||
@@ -179,82 +159,20 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
@Override
|
||||
public void addContact(Transaction txn, ContactId c, SecretKey master,
|
||||
long timestamp, boolean alice) throws DbException {
|
||||
deriveAndAddKeys(txn, c, master, timestamp, alice, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeySetId addUnboundKeys(Transaction txn, SecretKey master,
|
||||
long timestamp, boolean alice) throws DbException {
|
||||
return deriveAndAddKeys(txn, null, master, timestamp, alice, false);
|
||||
}
|
||||
|
||||
private KeySetId deriveAndAddKeys(Transaction txn, @Nullable ContactId c,
|
||||
SecretKey master, long timestamp, boolean alice, boolean active)
|
||||
throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
// Work out what rotation period the timestamp belongs to
|
||||
long rotationPeriod = timestamp / rotationPeriodLength;
|
||||
// Derive the transport keys
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, rotationPeriod, alice, active);
|
||||
master, rotationPeriod, alice);
|
||||
// Rotate the keys to the current rotation period if necessary
|
||||
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
||||
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||
// Write the keys back to the DB
|
||||
KeySetId keySetId = db.addTransportKeys(txn, c, k);
|
||||
// Initialise mutable state for the contact
|
||||
addKeys(keySetId, c, new MutableTransportKeys(k));
|
||||
return keySetId;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindKeys(Transaction txn, ContactId c, KeySetId k)
|
||||
throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
MutableKeySet ks = keys.get(k);
|
||||
if (ks == null) throw new IllegalArgumentException();
|
||||
// Check that the keys haven't already been bound
|
||||
if (ks.getContactId() != null) throw new IllegalArgumentException();
|
||||
MutableTransportKeys m = ks.getTransportKeys();
|
||||
addKeys(k, c, m);
|
||||
db.bindTransportKeys(txn, c, m.getTransportId(), k);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateKeys(Transaction txn, KeySetId k) throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
MutableKeySet ks = keys.get(k);
|
||||
if (ks == null) throw new IllegalArgumentException();
|
||||
// Check that the keys have been bound
|
||||
if (ks.getContactId() == null) throw new IllegalArgumentException();
|
||||
MutableTransportKeys m = ks.getTransportKeys();
|
||||
m.getCurrentOutgoingKeys().activate();
|
||||
considerReplacingOutgoingKeys(ks);
|
||||
db.setTransportKeysActive(txn, m.getTransportId(), k);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeKeys(Transaction txn, KeySetId k) throws DbException {
|
||||
lock.lock();
|
||||
try {
|
||||
MutableKeySet ks = keys.remove(k);
|
||||
if (ks == null) throw new IllegalArgumentException();
|
||||
// Check that the keys haven't been bound
|
||||
if (ks.getContactId() != null) throw new IllegalArgumentException();
|
||||
TransportId t = ks.getTransportKeys().getTransportId();
|
||||
db.removeTransportKeys(txn, t, k);
|
||||
addKeys(c, new MutableTransportKeys(k));
|
||||
// Write the keys back to the DB
|
||||
db.addTransportKeys(txn, c, k);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -265,29 +183,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
lock.lock();
|
||||
try {
|
||||
// Remove mutable state for the contact
|
||||
Iterator<TagContext> it = inContexts.values().iterator();
|
||||
while (it.hasNext()) if (it.next().contactId.equals(c)) it.remove();
|
||||
Iterator<Entry<Bytes, TagContext>> it =
|
||||
inContexts.entrySet().iterator();
|
||||
while (it.hasNext())
|
||||
if (it.next().getValue().contactId.equals(c)) it.remove();
|
||||
outContexts.remove(c);
|
||||
Iterator<MutableKeySet> it1 = keys.values().iterator();
|
||||
while (it1.hasNext()) {
|
||||
ContactId c1 = it1.next().getContactId();
|
||||
if (c1 != null && c1.equals(c)) it1.remove();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSendOutgoingStreams(ContactId c) {
|
||||
lock.lock();
|
||||
try {
|
||||
MutableKeySet ks = outContexts.get(c);
|
||||
if (ks == null) return false;
|
||||
MutableOutgoingKeys outKeys =
|
||||
ks.getTransportKeys().getCurrentOutgoingKeys();
|
||||
if (!outKeys.isActive()) throw new AssertionError();
|
||||
return outKeys.getStreamCounter() <= MAX_32_BIT_UNSIGNED;
|
||||
keys.remove(c);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -299,11 +200,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
lock.lock();
|
||||
try {
|
||||
// Look up the outgoing keys for the contact
|
||||
MutableKeySet ks = outContexts.get(c);
|
||||
if (ks == null) return null;
|
||||
MutableOutgoingKeys outKeys =
|
||||
ks.getTransportKeys().getCurrentOutgoingKeys();
|
||||
if (!outKeys.isActive()) throw new AssertionError();
|
||||
MutableOutgoingKeys outKeys = outContexts.get(c);
|
||||
if (outKeys == null) return null;
|
||||
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
|
||||
// Create a stream context
|
||||
StreamContext ctx = new StreamContext(c, transportId,
|
||||
@@ -311,7 +209,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
outKeys.getStreamCounter());
|
||||
// Increment the stream counter and write it back to the DB
|
||||
outKeys.incrementStreamCounter();
|
||||
db.incrementStreamCounter(txn, transportId, ks.getKeySetId());
|
||||
db.incrementStreamCounter(txn, c, transportId,
|
||||
outKeys.getRotationPeriod());
|
||||
return ctx;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
@@ -339,9 +238,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
byte[] addTag = new byte[TAG_LENGTH];
|
||||
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
|
||||
PROTOCOL_VERSION, streamNumber);
|
||||
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
|
||||
tagCtx.contactId, inKeys, streamNumber);
|
||||
inContexts.put(new Bytes(addTag), tagCtx1);
|
||||
inContexts.put(new Bytes(addTag), new TagContext(
|
||||
tagCtx.contactId, inKeys, streamNumber));
|
||||
}
|
||||
// Remove tags for any stream numbers removed from the window
|
||||
for (long streamNumber : change.getRemoved()) {
|
||||
@@ -352,19 +250,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
inContexts.remove(new Bytes(removeTag));
|
||||
}
|
||||
// Write the window back to the DB
|
||||
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
|
||||
db.setReorderingWindow(txn, tagCtx.contactId, transportId,
|
||||
inKeys.getRotationPeriod(), window.getBase(),
|
||||
window.getBitmap());
|
||||
// If the outgoing keys are inactive, activate them
|
||||
MutableKeySet ks = keys.get(tagCtx.keySetId);
|
||||
MutableOutgoingKeys outKeys =
|
||||
ks.getTransportKeys().getCurrentOutgoingKeys();
|
||||
if (!outKeys.isActive()) {
|
||||
LOG.info("Activating outgoing keys");
|
||||
outKeys.activate();
|
||||
considerReplacingOutgoingKeys(ks);
|
||||
db.setTransportKeysActive(txn, transportId, tagCtx.keySetId);
|
||||
}
|
||||
return ctx;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
@@ -376,11 +264,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
lock.lock();
|
||||
try {
|
||||
// Rotate the keys to the current rotation period
|
||||
Collection<KeySet> snapshot = new ArrayList<>(keys.size());
|
||||
for (MutableKeySet ks : keys.values()) {
|
||||
snapshot.add(new KeySet(ks.getKeySetId(), ks.getContactId(),
|
||||
ks.getTransportKeys().snapshot()));
|
||||
}
|
||||
Map<ContactId, TransportKeys> snapshot = new HashMap<>();
|
||||
for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet())
|
||||
snapshot.put(e.getKey(), e.getValue().snapshot());
|
||||
RotationResult rotationResult = rotateKeys(snapshot, now);
|
||||
// Rebuild the mutable state for all contacts
|
||||
inContexts.clear();
|
||||
@@ -399,14 +285,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
|
||||
private static class TagContext {
|
||||
|
||||
private final KeySetId keySetId;
|
||||
private final ContactId contactId;
|
||||
private final MutableIncomingKeys inKeys;
|
||||
private final long streamNumber;
|
||||
|
||||
private TagContext(KeySetId keySetId, ContactId contactId,
|
||||
MutableIncomingKeys inKeys, long streamNumber) {
|
||||
this.keySetId = keySetId;
|
||||
private TagContext(ContactId contactId, MutableIncomingKeys inKeys,
|
||||
long streamNumber) {
|
||||
this.contactId = contactId;
|
||||
this.inKeys = inKeys;
|
||||
this.streamNumber = streamNumber;
|
||||
@@ -415,7 +299,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
||||
|
||||
private static class RotationResult {
|
||||
|
||||
private final Collection<KeySet> current = new ArrayList<>();
|
||||
private final Collection<KeySet> rotated = new ArrayList<>();
|
||||
private final Map<ContactId, TransportKeys> current, rotated;
|
||||
|
||||
private RotationResult() {
|
||||
current = new HashMap<>();
|
||||
rotated = new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import java.util.Random;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
@@ -301,34 +300,30 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testVerifySignature() throws Exception {
|
||||
byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
byte[] publicKey = getRandomBytes(42);
|
||||
byte[] signed = expectToByteArray(list);
|
||||
byte[] bytes = expectToByteArray(list);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verifySignature(signature, label, signed,
|
||||
publicKey);
|
||||
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
|
||||
will(returnValue(true));
|
||||
}});
|
||||
|
||||
clientHelper.verifySignature(signature, label, list, publicKey);
|
||||
clientHelper.verifySignature(label, rawMessage, publicKey, list);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyWrongSignature() throws Exception {
|
||||
byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
byte[] publicKey = getRandomBytes(42);
|
||||
byte[] signed = expectToByteArray(list);
|
||||
byte[] bytes = expectToByteArray(list);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verifySignature(signature, label, signed,
|
||||
publicKey);
|
||||
oneOf(cryptoComponent).verify(label, bytes, publicKey, rawMessage);
|
||||
will(returnValue(false));
|
||||
}});
|
||||
|
||||
try {
|
||||
clientHelper.verifySignature(signature, label, list, publicKey);
|
||||
clientHelper.verifySignature(label, rawMessage, publicKey, list);
|
||||
fail();
|
||||
} catch (GeneralSecurityException e) {
|
||||
// expected
|
||||
|
||||
@@ -143,9 +143,9 @@ public class EdSignatureTest extends SignatureTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verify(byte[] signature, String label, byte[] signed,
|
||||
byte[] publicKey) throws GeneralSecurityException {
|
||||
return crypto.verifySignature(signature, label, signed, publicKey);
|
||||
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||
byte[] signature) throws GeneralSecurityException {
|
||||
return crypto.verify(label, signedData, publicKey, signature);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -17,7 +17,6 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -28,13 +27,13 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||
private final TransportCrypto transportCrypto =
|
||||
new TransportCryptoImpl(crypto);
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final TransportId transportId = new TransportId("id");
|
||||
private final SecretKey master = getSecretKey();
|
||||
|
||||
@Test
|
||||
public void testKeysAreDistinct() {
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
master, 123, true);
|
||||
assertAllDifferent(k);
|
||||
}
|
||||
|
||||
@@ -42,9 +41,9 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
||||
// Start in rotation period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
master, 123, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, false, true);
|
||||
master, 123, false);
|
||||
// Alice's incoming keys should equal Bob's outgoing keys
|
||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||
@@ -74,9 +73,9 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
||||
// Start in rotation period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
master, 123, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, false, true);
|
||||
master, 123, false);
|
||||
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||
// in period 455
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
@@ -101,9 +100,9 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
public void testNextKeysMatchNextKeysOfContact() {
|
||||
// Start in rotation period 123
|
||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
master, 123, true);
|
||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, false, true);
|
||||
master, 123, false);
|
||||
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||
// period 455
|
||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||
@@ -128,20 +127,20 @@ public class KeyDerivationTest extends BrambleTestCase {
|
||||
SecretKey master1 = getSecretKey();
|
||||
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
master, 123, true);
|
||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
|
||||
master1, 123, true, true);
|
||||
master1, 123, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportIdAffectsOutput() {
|
||||
TransportId transportId1 = getTransportId();
|
||||
TransportId transportId1 = new TransportId("id1");
|
||||
assertFalse(transportId.getString().equals(transportId1.getString()));
|
||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||
master, 123, true, true);
|
||||
master, 123, true);
|
||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
|
||||
master, 123, true, true);
|
||||
master, 123, true);
|
||||
assertAllDifferent(k, k1);
|
||||
}
|
||||
|
||||
|
||||
@@ -126,13 +126,13 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
||||
byte[] signature = crypto.sign("test", message,
|
||||
privateKey.getEncoded());
|
||||
// Verify the signature
|
||||
assertTrue(crypto.verifySignature(signature, "test", message,
|
||||
publicKey.getEncoded()));
|
||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||
signature));
|
||||
// Encode and parse the public key - no exceptions should be thrown
|
||||
publicKey = parser.parsePublicKey(publicKey.getEncoded());
|
||||
// Verify the signature again
|
||||
assertTrue(crypto.verifySignature(signature, "test", message,
|
||||
publicKey.getEncoded()));
|
||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||
signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,15 +146,15 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
||||
byte[] signature = crypto.sign("test", message,
|
||||
privateKey.getEncoded());
|
||||
// Verify the signature
|
||||
assertTrue(crypto.verifySignature(signature, "test", message,
|
||||
publicKey.getEncoded()));
|
||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||
signature));
|
||||
// Encode and parse the private key - no exceptions should be thrown
|
||||
privateKey = parser.parsePrivateKey(privateKey.getEncoded());
|
||||
// Sign the data again - the signatures should be the same
|
||||
byte[] signature1 = crypto.sign("test", message,
|
||||
privateKey.getEncoded());
|
||||
assertTrue(crypto.verifySignature(signature1, "test", message,
|
||||
publicKey.getEncoded()));
|
||||
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||
signature1));
|
||||
assertArrayEquals(signature, signature1);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ 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.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MacTest extends BrambleTestCase {
|
||||
|
||||
@@ -33,7 +32,6 @@ public class MacTest extends BrambleTestCase {
|
||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||
byte[] mac1 = crypto.mac(label1, key1, input1, input2, input3);
|
||||
assertArrayEquals(mac, mac1);
|
||||
assertTrue(crypto.verifyMac(mac, label1, key1, input1, input2, input3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,11 +40,6 @@ public class MacTest extends BrambleTestCase {
|
||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||
byte[] mac1 = crypto.mac(label2, key1, input1, input2, input3);
|
||||
assertFalse(Arrays.equals(mac, mac1));
|
||||
// Each MAC should fail to verify with the other MAC's label
|
||||
assertFalse(crypto.verifyMac(mac, label2, key1, input1, input2,
|
||||
input3));
|
||||
assertFalse(crypto.verifyMac(mac1, label1, key2, input1, input2,
|
||||
input3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,11 +48,6 @@ public class MacTest extends BrambleTestCase {
|
||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||
byte[] mac1 = crypto.mac(label1, key2, input1, input2, input3);
|
||||
assertFalse(Arrays.equals(mac, mac1));
|
||||
// Each MAC should fail to verify with the other MAC's key
|
||||
assertFalse(crypto.verifyMac(mac, label1, key2, input1, input2,
|
||||
input3));
|
||||
assertFalse(crypto.verifyMac(mac1, label2, key1, input1, input2,
|
||||
input3));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -69,11 +57,6 @@ public class MacTest extends BrambleTestCase {
|
||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||
byte[] mac1 = crypto.mac(label1, key1, input3, input2, input1);
|
||||
assertFalse(Arrays.equals(mac, mac1));
|
||||
// Each MAC should fail to verify with the other MAC's inputs
|
||||
assertFalse(crypto.verifyMac(mac, label1, key2, input3, input2,
|
||||
input1));
|
||||
assertFalse(crypto.verifyMac(mac1, label1, key1, input1, input2,
|
||||
input3));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ public abstract class SignatureTest extends BrambleTestCase {
|
||||
protected abstract byte[] sign(String label, byte[] toSign,
|
||||
byte[] privateKey) throws GeneralSecurityException;
|
||||
|
||||
protected abstract boolean verify(byte[] signature, String label,
|
||||
byte[] signed, byte[] publicKey) throws GeneralSecurityException;
|
||||
protected abstract boolean verify(String label, byte[] signedData,
|
||||
byte[] publicKey, byte[] signature) throws GeneralSecurityException;
|
||||
|
||||
SignatureTest() {
|
||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||
@@ -85,7 +85,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testSignatureVerification() throws Exception {
|
||||
byte[] sig = sign(label, inputBytes, privateKey);
|
||||
assertTrue(verify(sig, label, inputBytes, publicKey));
|
||||
assertTrue(verify(label, inputBytes, publicKey, sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -95,7 +95,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||
// calculate the signature with different key, should fail to verify
|
||||
byte[] sig = sign(label, inputBytes, privateKey2);
|
||||
assertFalse(verify(sig, label, inputBytes, publicKey));
|
||||
assertFalse(verify(label, inputBytes, publicKey, sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -104,7 +104,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||
// calculate the signature with different input, should fail to verify
|
||||
byte[] sig = sign(label, inputBytes, privateKey);
|
||||
assertFalse(verify(sig, label, inputBytes2, publicKey));
|
||||
assertFalse(verify(label, inputBytes2, publicKey, sig));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,7 +113,7 @@ public abstract class SignatureTest extends BrambleTestCase {
|
||||
String label2 = StringUtils.getRandomString(42);
|
||||
// calculate the signature with different label, should fail to verify
|
||||
byte[] sig = sign(label, inputBytes, privateKey);
|
||||
assertFalse(verify(sig, label2, inputBytes, publicKey));
|
||||
assertFalse(verify(label2, inputBytes, publicKey, sig));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,36 +44,34 @@ import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
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.TestUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
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.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
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.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -102,28 +100,27 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
private final int maxLatency;
|
||||
private final ContactId contactId;
|
||||
private final Contact contact;
|
||||
private final KeySetId keySetId;
|
||||
|
||||
public DatabaseComponentImplTest() {
|
||||
clientId = getClientId();
|
||||
group = getGroup(clientId);
|
||||
groupId = group.getId();
|
||||
clientId = new ClientId(getRandomString(123));
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
author = getAuthor();
|
||||
localAuthor = getLocalAuthor();
|
||||
messageId = new MessageId(getRandomId());
|
||||
messageId1 = new MessageId(getRandomId());
|
||||
messageId = new MessageId(TestUtils.getRandomId());
|
||||
messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
long timestamp = System.currentTimeMillis();
|
||||
size = 1234;
|
||||
raw = new byte[size];
|
||||
message = new Message(messageId, groupId, timestamp, raw);
|
||||
metadata = new Metadata();
|
||||
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
||||
transportId = getTransportId();
|
||||
transportId = new TransportId("id");
|
||||
maxLatency = Integer.MAX_VALUE;
|
||||
contactId = new ContactId(234);
|
||||
contact = new Contact(contactId, author, localAuthor.getId(),
|
||||
true, true);
|
||||
keySetId = new KeySetId(345);
|
||||
}
|
||||
|
||||
private DatabaseComponent createDatabaseComponent(Database<Object> database,
|
||||
@@ -137,7 +134,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
int shutdownHandle = 12345;
|
||||
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));
|
||||
@@ -204,7 +201,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.addLocalAuthor(transaction, localAuthor);
|
||||
@@ -285,11 +282,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
// Check whether the contact is in the DB (which it's not)
|
||||
exactly(17).of(database).startTransaction();
|
||||
exactly(18).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
exactly(17).of(database).containsContact(txn, contactId);
|
||||
exactly(18).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(false));
|
||||
exactly(17).of(database).abortTransaction(txn);
|
||||
exactly(18).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -304,16 +301,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.bindTransportKeys(transaction, contactId, transportId, keySetId);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.generateAck(transaction, contactId, 123);
|
||||
@@ -384,6 +371,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.incrementStreamCounter(transaction, contactId, transportId, 0);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.getGroupVisibility(transaction, contactId, groupId);
|
||||
@@ -457,6 +454,17 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
|
||||
new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
// Expected
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.setGroupVisibility(transaction, contactId, groupId, SHARED);
|
||||
@@ -769,13 +777,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// endTransaction()
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// Check whether the transport is in the DB (which it's not)
|
||||
exactly(6).of(database).startTransaction();
|
||||
exactly(4).of(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
exactly(2).of(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
exactly(6).of(database).containsTransport(txn, transportId);
|
||||
exactly(4).of(database).containsTransport(txn, transportId);
|
||||
will(returnValue(false));
|
||||
exactly(6).of(database).abortTransaction(txn);
|
||||
exactly(4).of(database).abortTransaction(txn);
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -790,16 +798,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.bindTransportKeys(transaction, contactId, transportId, keySetId);
|
||||
fail();
|
||||
} catch (NoSuchTransportException expected) {
|
||||
// Expected
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.getTransportKeys(transaction, transportId);
|
||||
@@ -812,7 +810,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.incrementStreamCounter(transaction, transportId, keySetId);
|
||||
db.incrementStreamCounter(transaction, contactId, transportId, 0);
|
||||
fail();
|
||||
} catch (NoSuchTransportException expected) {
|
||||
// Expected
|
||||
@@ -832,17 +830,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.removeTransportKeys(transaction, transportId, keySetId);
|
||||
fail();
|
||||
} catch (NoSuchTransportException expected) {
|
||||
// Expected
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.setReorderingWindow(transaction, keySetId, transportId, 0, 0,
|
||||
db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
|
||||
new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||
fail();
|
||||
} catch (NoSuchTransportException expected) {
|
||||
@@ -919,7 +907,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testGenerateOffer() throws Exception {
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
@@ -950,7 +938,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testGenerateRequest() throws Exception {
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1138,9 +1126,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testReceiveOffer() throws Exception {
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId2 = new MessageId(getRandomId());
|
||||
MessageId messageId3 = new MessageId(getRandomId());
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1315,13 +1303,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys();
|
||||
Collection<KeySet> keys =
|
||||
singletonList(new KeySet(keySetId, contactId, transportKeys));
|
||||
Map<ContactId, TransportKeys> keys =
|
||||
singletonMap(contactId, transportKeys);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
// updateTransportKeys()
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsTransport(txn, transportId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).updateTransportKeys(txn, keys);
|
||||
@@ -1347,22 +1337,22 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
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, true);
|
||||
2, 456);
|
||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
|
||||
@@ -1508,10 +1498,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMessageDependencies() throws Exception {
|
||||
int shutdownHandle = 12345;
|
||||
MessageId messageId2 = new MessageId(getRandomId());
|
||||
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));
|
||||
@@ -1528,12 +1518,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
// addMessageDependencies()
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getMessageState(txn, messageId);
|
||||
will(returnValue(DELIVERED));
|
||||
oneOf(database).addMessageDependency(txn, message, messageId1,
|
||||
DELIVERED);
|
||||
oneOf(database).addMessageDependency(txn, message, messageId2,
|
||||
DELIVERED);
|
||||
oneOf(database).addMessageDependency(txn, groupId, messageId,
|
||||
messageId1);
|
||||
oneOf(database).addMessageDependency(txn, groupId, messageId,
|
||||
messageId2);
|
||||
// getMessageDependencies()
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
@@ -1555,7 +1543,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
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);
|
||||
|
||||
@@ -62,7 +62,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
public void testDoesNotRunMigrationsWhenCreatingDatabase()
|
||||
throws Exception {
|
||||
Database<Connection> db = createDatabase(singletonList(migration));
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
@@ -72,14 +72,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
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);
|
||||
db.open();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,12 +87,12 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
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));
|
||||
assertTrue(db.open());
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
@@ -101,14 +101,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
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);
|
||||
db.open();
|
||||
}
|
||||
|
||||
@Test(expected = DataTooOldException.class)
|
||||
@@ -116,13 +116,13 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(emptyList());
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
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);
|
||||
db.open();
|
||||
}
|
||||
|
||||
@Test(expected = DataTooOldException.class)
|
||||
@@ -141,14 +141,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
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);
|
||||
db.open();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,14 +170,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
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));
|
||||
assertTrue(db.open());
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
@@ -202,14 +202,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open(null));
|
||||
assertFalse(db.open());
|
||||
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));
|
||||
assertTrue(db.open());
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public abstract class DatabasePerformanceComparisonTest
|
||||
throws DbException {
|
||||
Database<Connection> db = createDatabase(conditionA,
|
||||
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
|
||||
db.open(null);
|
||||
db.open();
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
@@ -477,30 +477,30 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetMessagesToShare() throws Exception {
|
||||
String name = "getMessagesToShare(T)";
|
||||
String name = "getMessagesToShare(T, ClientId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
db.getMessagesToShare(txn);
|
||||
db.getMessagesToShare(txn, pickRandom(clientIds));
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMessagesToValidate() throws Exception {
|
||||
String name = "getMessagesToValidate(T)";
|
||||
String name = "getMessagesToValidate(T, ClientId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
db.getMessagesToValidate(txn);
|
||||
db.getMessagesToValidate(txn, pickRandom(clientIds));
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPendingMessages() throws Exception {
|
||||
String name = "getPendingMessages(T)";
|
||||
String name = "getPendingMessages(T, ClientId)";
|
||||
benchmark(name, db -> {
|
||||
Connection txn = db.startTransaction();
|
||||
db.getPendingMessages(txn);
|
||||
db.getPendingMessages(txn, pickRandom(clientIds));
|
||||
db.commitTransaction(txn);
|
||||
});
|
||||
}
|
||||
@@ -572,9 +572,8 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
messageMeta.get(g.getId()).add(mm);
|
||||
db.mergeMessageMetadata(txn, m.getId(), mm);
|
||||
if (k > 0) {
|
||||
MessageId dependency =
|
||||
pickRandom(groupMessages.get(g.getId()));
|
||||
db.addMessageDependency(txn, m, dependency, state);
|
||||
db.addMessageDependency(txn, g.getId(), m.getId(),
|
||||
pickRandom(groupMessages.get(g.getId())));
|
||||
}
|
||||
groupMessages.get(g.getId()).add(m.getId());
|
||||
}
|
||||
@@ -599,9 +598,8 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
||||
messageMeta.get(g.getId()).add(mm);
|
||||
db.mergeMessageMetadata(txn, m.getId(), mm);
|
||||
if (j > 0) {
|
||||
MessageId dependency =
|
||||
pickRandom(groupMessages.get(g.getId()));
|
||||
db.addMessageDependency(txn, m, dependency, DELIVERED);
|
||||
db.addMessageDependency(txn, g.getId(), m.getId(),
|
||||
pickRandom(groupMessages.get(g.getId())));
|
||||
}
|
||||
groupMessages.get(g.getId()).add(m.getId());
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
|
||||
private Database<Connection> openDatabase() throws DbException {
|
||||
Database<Connection> db = createDatabase(
|
||||
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
|
||||
db.open(null);
|
||||
db.open();
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ 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.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.system.SystemClock;
|
||||
@@ -36,6 +34,7 @@ import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -43,28 +42,23 @@ import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
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.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
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.test.TestUtils.getTransportId;
|
||||
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;
|
||||
@@ -92,12 +86,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
private final Message message;
|
||||
private final TransportId transportId;
|
||||
private final ContactId contactId;
|
||||
private final KeySetId keySetId, keySetId1;
|
||||
|
||||
JdbcDatabaseTest() throws Exception {
|
||||
clientId = getClientId();
|
||||
group = getGroup(clientId);
|
||||
groupId = group.getId();
|
||||
groupId = new GroupId(getRandomId());
|
||||
clientId = new ClientId(getRandomString(123));
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
author = getAuthor();
|
||||
localAuthor = getLocalAuthor();
|
||||
messageId = new MessageId(getRandomId());
|
||||
@@ -105,10 +99,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
size = 1234;
|
||||
raw = getRandomBytes(size);
|
||||
message = new Message(messageId, groupId, timestamp, raw);
|
||||
transportId = getTransportId();
|
||||
transportId = new TransportId("id");
|
||||
contactId = new ContactId(1);
|
||||
keySetId = new KeySetId(1);
|
||||
keySetId1 = new KeySetId(2);
|
||||
}
|
||||
|
||||
protected abstract JdbcDatabase createDatabase(DatabaseConfig config,
|
||||
@@ -198,9 +190,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// The contact has not seen the message, so it should be sendable
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
// Changing the status to seen = true should make the message unsendable
|
||||
db.raiseSeenFlag(txn, contactId, messageId);
|
||||
@@ -236,9 +228,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Marking the message delivered should make it sendable
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
// Marking the message invalid should make it unsendable
|
||||
db.setMessageState(txn, messageId, INVALID);
|
||||
@@ -287,9 +279,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Sharing the group should make the message sendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
// Unsharing the group should make the message unsendable
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
@@ -332,9 +324,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Sharing the message should make it sendable
|
||||
db.setMessageShared(txn, messageId);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -360,7 +352,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// The message is just the right size to send
|
||||
ids = db.getMessagesToSend(txn, contactId, size);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -392,7 +384,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.lowerAckFlag(txn, contactId, Arrays.asList(messageId, messageId1));
|
||||
|
||||
// Both message IDs should have been removed
|
||||
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
||||
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
|
||||
contactId, 1234));
|
||||
|
||||
// Raise the ack flag again
|
||||
@@ -423,7 +415,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Retrieve the message from the database and mark it as sent
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE);
|
||||
|
||||
// The message should no longer be sendable
|
||||
@@ -634,31 +626,31 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
|
||||
// The group should not be visible to the contact
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(emptyMap(),
|
||||
assertEquals(Collections.emptyMap(),
|
||||
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(singletonMap(contactId, false),
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Share the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(singletonMap(contactId, true),
|
||||
assertEquals(Collections.singletonMap(contactId, true),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Unshare the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(singletonMap(contactId, false),
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Make the group invisible again
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(emptyMap(),
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -668,125 +660,48 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys keys = createTransportKeys();
|
||||
TransportKeys keys1 = createTransportKeys();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Initially there should be no transport keys in the database
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getTransportKeys(txn, transportId));
|
||||
|
||||
// Add the contact, the transport and the transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(keySetId, db.addTransportKeys(txn, contactId, keys));
|
||||
assertEquals(keySetId1, db.addTransportKeys(txn, contactId, keys1));
|
||||
db.addTransportKeys(txn, contactId, keys);
|
||||
|
||||
// Retrieve the transport keys
|
||||
Collection<KeySet> allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (KeySet ks : allKeys) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
if (ks.getKeySetId().equals(keySetId)) {
|
||||
assertKeysEquals(keys, ks.getTransportKeys());
|
||||
} else {
|
||||
assertEquals(keySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(keys1, ks.getTransportKeys());
|
||||
}
|
||||
}
|
||||
Map<ContactId, TransportKeys> newKeys =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
assertEquals(1, newKeys.size());
|
||||
Entry<ContactId, TransportKeys> e =
|
||||
newKeys.entrySet().iterator().next();
|
||||
assertEquals(contactId, e.getKey());
|
||||
TransportKeys k = e.getValue();
|
||||
assertEquals(transportId, k.getTransportId());
|
||||
assertKeysEquals(keys.getPreviousIncomingKeys(),
|
||||
k.getPreviousIncomingKeys());
|
||||
assertKeysEquals(keys.getCurrentIncomingKeys(),
|
||||
k.getCurrentIncomingKeys());
|
||||
assertKeysEquals(keys.getNextIncomingKeys(),
|
||||
k.getNextIncomingKeys());
|
||||
assertKeysEquals(keys.getCurrentOutgoingKeys(),
|
||||
k.getCurrentOutgoingKeys());
|
||||
|
||||
// Removing the contact should remove the transport keys
|
||||
db.removeContact(txn, contactId);
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getTransportKeys(txn, transportId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnboundTransportKeys() throws Exception {
|
||||
TransportKeys keys = createTransportKeys();
|
||||
TransportKeys keys1 = createTransportKeys();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Initially there should be no transport keys in the database
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
|
||||
// Add the contact, the transport and the unbound transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
assertEquals(keySetId, db.addTransportKeys(txn, null, keys));
|
||||
assertEquals(keySetId1, db.addTransportKeys(txn, null, keys1));
|
||||
|
||||
// Retrieve the transport keys
|
||||
Collection<KeySet> allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (KeySet ks : allKeys) {
|
||||
assertNull(ks.getContactId());
|
||||
if (ks.getKeySetId().equals(keySetId)) {
|
||||
assertKeysEquals(keys, ks.getTransportKeys());
|
||||
} else {
|
||||
assertEquals(keySetId1, ks.getKeySetId());
|
||||
assertKeysEquals(keys1, ks.getTransportKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the first set of transport keys
|
||||
db.bindTransportKeys(txn, contactId, transportId, keySetId);
|
||||
|
||||
// Retrieve the keys again - the first set should be bound
|
||||
allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(2, allKeys.size());
|
||||
for (KeySet ks : allKeys) {
|
||||
if (ks.getKeySetId().equals(keySetId)) {
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
assertKeysEquals(keys, ks.getTransportKeys());
|
||||
} else {
|
||||
assertEquals(keySetId1, ks.getKeySetId());
|
||||
assertNull(ks.getContactId());
|
||||
assertKeysEquals(keys1, ks.getTransportKeys());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the unbound transport keys
|
||||
db.removeTransportKeys(txn, transportId, keySetId1);
|
||||
|
||||
// Retrieve the keys again - the second set should be gone
|
||||
allKeys = db.getTransportKeys(txn, transportId);
|
||||
assertEquals(1, allKeys.size());
|
||||
KeySet ks = allKeys.iterator().next();
|
||||
assertEquals(keySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
assertKeysEquals(keys, ks.getTransportKeys());
|
||||
|
||||
// Removing the transport should remove the remaining transport keys
|
||||
db.removeTransport(txn, transportId);
|
||||
assertEquals(emptyList(), db.getTransportKeys(txn, transportId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
private void assertKeysEquals(TransportKeys expected,
|
||||
TransportKeys actual) {
|
||||
assertEquals(expected.getTransportId(), actual.getTransportId());
|
||||
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||
assertKeysEquals(expected.getPreviousIncomingKeys(),
|
||||
actual.getPreviousIncomingKeys());
|
||||
assertKeysEquals(expected.getCurrentIncomingKeys(),
|
||||
actual.getCurrentIncomingKeys());
|
||||
assertKeysEquals(expected.getNextIncomingKeys(),
|
||||
actual.getNextIncomingKeys());
|
||||
assertKeysEquals(expected.getCurrentOutgoingKeys(),
|
||||
actual.getCurrentOutgoingKeys());
|
||||
}
|
||||
|
||||
private void assertKeysEquals(IncomingKeys expected, IncomingKeys actual) {
|
||||
assertArrayEquals(expected.getTagKey().getBytes(),
|
||||
actual.getTagKey().getBytes());
|
||||
@@ -804,7 +719,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
actual.getHeaderKey().getBytes());
|
||||
assertEquals(expected.getRotationPeriod(), actual.getRotationPeriod());
|
||||
assertEquals(expected.getStreamCounter(), actual.getStreamCounter());
|
||||
assertEquals(expected.isActive(), actual.isActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -821,18 +735,18 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.updateTransportKeys(txn,
|
||||
singletonList(new KeySet(keySetId, contactId, keys)));
|
||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||
|
||||
// Increment the stream counter twice and retrieve the transport keys
|
||||
db.incrementStreamCounter(txn, transportId, keySetId);
|
||||
db.incrementStreamCounter(txn, transportId, keySetId);
|
||||
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
|
||||
db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
|
||||
db.incrementStreamCounter(txn, contactId, transportId, rotationPeriod);
|
||||
Map<ContactId, TransportKeys> newKeys =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
assertEquals(1, newKeys.size());
|
||||
KeySet ks = newKeys.iterator().next();
|
||||
assertEquals(keySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
Entry<ContactId, TransportKeys> e =
|
||||
newKeys.entrySet().iterator().next();
|
||||
assertEquals(contactId, e.getKey());
|
||||
TransportKeys k = e.getValue();
|
||||
assertEquals(transportId, k.getTransportId());
|
||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||
assertEquals(rotationPeriod, outCurr.getRotationPeriod());
|
||||
@@ -857,19 +771,19 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.updateTransportKeys(txn,
|
||||
singletonList(new KeySet(keySetId, contactId, keys)));
|
||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||
|
||||
// Update the reordering window and retrieve the transport keys
|
||||
new Random().nextBytes(bitmap);
|
||||
db.setReorderingWindow(txn, keySetId, transportId, rotationPeriod,
|
||||
db.setReorderingWindow(txn, contactId, transportId, rotationPeriod,
|
||||
base + 1, bitmap);
|
||||
Collection<KeySet> newKeys = db.getTransportKeys(txn, transportId);
|
||||
Map<ContactId, TransportKeys> newKeys =
|
||||
db.getTransportKeys(txn, transportId);
|
||||
assertEquals(1, newKeys.size());
|
||||
KeySet ks = newKeys.iterator().next();
|
||||
assertEquals(keySetId, ks.getKeySetId());
|
||||
assertEquals(contactId, ks.getContactId());
|
||||
TransportKeys k = ks.getTransportKeys();
|
||||
Entry<ContactId, TransportKeys> e =
|
||||
newKeys.entrySet().iterator().next();
|
||||
assertEquals(contactId, e.getKey());
|
||||
TransportKeys k = e.getValue();
|
||||
assertEquals(transportId, k.getTransportId());
|
||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||
assertEquals(rotationPeriod, inCurr.getRotationPeriod());
|
||||
@@ -916,18 +830,18 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
Collection<ContactId> contacts =
|
||||
db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(emptyList(), contacts);
|
||||
assertEquals(Collections.emptyList(), contacts);
|
||||
|
||||
// Add a contact associated with the local author
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
contacts = db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(singletonList(contactId), contacts);
|
||||
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());
|
||||
assertEquals(emptyList(), contacts);
|
||||
assertEquals(Collections.emptyList(), contacts);
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -1313,8 +1227,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
MessageId messageId4 = new MessageId(getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
Message message2 = new Message(messageId2, groupId, timestamp, raw);
|
||||
Message message3 = new Message(messageId3, groupId, timestamp, raw);
|
||||
Message message4 = new Message(messageId4, groupId, timestamp, raw);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
@@ -1322,21 +1234,21 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// Add a group and some messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
db.addMessage(txn, message1, PENDING, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message2, INVALID, true, contactId);
|
||||
|
||||
// Add dependencies
|
||||
db.addMessageDependency(txn, message, messageId1, PENDING);
|
||||
db.addMessageDependency(txn, message, messageId2, PENDING);
|
||||
db.addMessageDependency(txn, message1, messageId3, PENDING);
|
||||
db.addMessageDependency(txn, message2, messageId4, INVALID);
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId2);
|
||||
db.addMessageDependency(txn, groupId, messageId1, messageId3);
|
||||
db.addMessageDependency(txn, groupId, messageId2, messageId4);
|
||||
|
||||
Map<MessageId, State> dependencies;
|
||||
|
||||
// Retrieve dependencies for root
|
||||
dependencies = db.getMessageDependencies(txn, messageId);
|
||||
assertEquals(2, dependencies.size());
|
||||
assertEquals(PENDING, dependencies.get(messageId1));
|
||||
assertEquals(DELIVERED, dependencies.get(messageId1));
|
||||
assertEquals(INVALID, dependencies.get(messageId2));
|
||||
|
||||
// Retrieve dependencies for message 1
|
||||
@@ -1369,24 +1281,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
assertEquals(1, dependents.size());
|
||||
assertEquals(PENDING, dependents.get(messageId));
|
||||
|
||||
// Message 3 is missing, so it has no dependents
|
||||
dependents = db.getMessageDependents(txn, messageId3);
|
||||
assertEquals(0, dependents.size());
|
||||
|
||||
// Add message 3
|
||||
db.addMessage(txn, message3, UNKNOWN, false, contactId);
|
||||
|
||||
// Message 3 has message 1 as a dependent
|
||||
dependents = db.getMessageDependents(txn, messageId3);
|
||||
assertEquals(1, dependents.size());
|
||||
assertEquals(PENDING, dependents.get(messageId1));
|
||||
|
||||
// Message 4 is missing, so it has no dependents
|
||||
dependents = db.getMessageDependents(txn, messageId4);
|
||||
assertEquals(0, dependents.size());
|
||||
|
||||
// Add message 4
|
||||
db.addMessage(txn, message4, UNKNOWN, false, contactId);
|
||||
assertEquals(DELIVERED, dependents.get(messageId1));
|
||||
|
||||
// Message 4 has message 2 as a dependent
|
||||
dependents = db.getMessageDependents(txn, messageId4);
|
||||
@@ -1407,8 +1305,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
|
||||
// Add a second group
|
||||
Group group1 = getGroup(clientId);
|
||||
GroupId groupId1 = group1.getId();
|
||||
GroupId groupId1 = new GroupId(getRandomId());
|
||||
Group group1 = new Group(groupId1, clientId,
|
||||
getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
|
||||
db.addGroup(txn, group1);
|
||||
|
||||
// Add a message to the second group
|
||||
@@ -1425,16 +1324,16 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, message3, DELIVERED, true, contactId);
|
||||
|
||||
// Add dependencies between the messages
|
||||
db.addMessageDependency(txn, message, messageId1, PENDING);
|
||||
db.addMessageDependency(txn, message, messageId2, PENDING);
|
||||
db.addMessageDependency(txn, message, messageId3, PENDING);
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId2);
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId3);
|
||||
|
||||
// Retrieve the dependencies for the root
|
||||
Map<MessageId, State> dependencies;
|
||||
dependencies = db.getMessageDependencies(txn, messageId);
|
||||
|
||||
// The cross-group dependency should have state UNKNOWN
|
||||
assertEquals(UNKNOWN, dependencies.get(messageId1));
|
||||
// The cross-group dependency should have state INVALID
|
||||
assertEquals(INVALID, dependencies.get(messageId1));
|
||||
|
||||
// The missing dependency should have state UNKNOWN
|
||||
assertEquals(UNKNOWN, dependencies.get(messageId2));
|
||||
@@ -1446,8 +1345,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Map<MessageId, State> dependents;
|
||||
dependents = db.getMessageDependents(txn, messageId1);
|
||||
|
||||
// The cross-group dependent should be excluded
|
||||
assertFalse(dependents.containsKey(messageId));
|
||||
// The cross-group dependent should have its real state
|
||||
assertEquals(PENDING, dependents.get(messageId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -1477,12 +1376,12 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
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));
|
||||
|
||||
@@ -1512,12 +1411,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||
|
||||
// Introduce dependencies between the messages
|
||||
db.addMessageDependency(txn, m1, mId2, DELIVERED);
|
||||
db.addMessageDependency(txn, m3, mId1, DELIVERED);
|
||||
db.addMessageDependency(txn, m4, mId3, DELIVERED);
|
||||
db.addMessageDependency(txn, groupId, mId1, mId2);
|
||||
db.addMessageDependency(txn, groupId, mId3, mId1);
|
||||
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));
|
||||
@@ -1645,9 +1545,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
// The message should be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(singletonList(messageId), ids);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
|
||||
// The raw message should not be null
|
||||
assertNotNull(db.getRawMessage(txn, messageId));
|
||||
@@ -1800,7 +1700,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
Database<Connection> db = createDatabase(
|
||||
new TestDatabaseConfig(testDir, MAX_SIZE), clock);
|
||||
if (!resume) TestUtils.deleteTestDirectory(testDir);
|
||||
db.open(null);
|
||||
db.open();
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -1820,7 +1720,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
||||
SecretKey outCurrTagKey = getSecretKey();
|
||||
SecretKey outCurrHeaderKey = getSecretKey();
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||
2, 456, true);
|
||||
2, 456);
|
||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public abstract class SingleDatabasePerformanceTest
|
||||
private Database<Connection> openDatabase() throws DbException {
|
||||
Database<Connection> db = createDatabase(
|
||||
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
|
||||
db.open(null);
|
||||
db.open();
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RE
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -32,7 +31,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
private final TransportConnectionWriter transportConnectionWriter =
|
||||
context.mock(TransportConnectionWriter.class);
|
||||
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final TransportId transportId = new TransportId("test");
|
||||
private final KeyAgreementConnection keyAgreementConnection =
|
||||
new KeyAgreementConnection(duplexTransportConnection, transportId);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
@@ -30,8 +29,8 @@ public class ConnectionRegistryImplTest extends BrambleTestCase {
|
||||
public ConnectionRegistryImplTest() {
|
||||
contactId = new ContactId(1);
|
||||
contactId1 = new ContactId(2);
|
||||
transportId = getTransportId();
|
||||
transportId1 = getTransportId();
|
||||
transportId = new TransportId("id");
|
||||
transportId1 = new TransportId("id1");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -24,8 +24,6 @@ import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
public class PluginManagerImplTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
@@ -48,21 +46,21 @@ public class PluginManagerImplTest extends BrambleTestCase {
|
||||
SimplexPluginFactory simplexFactory =
|
||||
context.mock(SimplexPluginFactory.class);
|
||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||
TransportId simplexId = getTransportId();
|
||||
TransportId simplexId = new TransportId("simplex");
|
||||
SimplexPluginFactory simplexFailFactory =
|
||||
context.mock(SimplexPluginFactory.class, "simplexFailFactory");
|
||||
SimplexPlugin simplexFailPlugin =
|
||||
context.mock(SimplexPlugin.class, "simplexFailPlugin");
|
||||
TransportId simplexFailId = getTransportId();
|
||||
TransportId simplexFailId = new TransportId("simplex1");
|
||||
|
||||
// Two duplex plugin factories: one creates a plugin, the other fails
|
||||
DuplexPluginFactory duplexFactory =
|
||||
context.mock(DuplexPluginFactory.class);
|
||||
DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
|
||||
TransportId duplexId = getTransportId();
|
||||
TransportId duplexId = new TransportId("duplex");
|
||||
DuplexPluginFactory duplexFailFactory =
|
||||
context.mock(DuplexPluginFactory.class, "duplexFailFactory");
|
||||
TransportId duplexFailId = getTransportId();
|
||||
TransportId duplexFailId = new TransportId("duplex1");
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
allowing(simplexPlugin).getId();
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
public class PollerTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -49,7 +48,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
private final SecureRandom random;
|
||||
|
||||
private final Executor ioExecutor = new ImmediateExecutor();
|
||||
private final TransportId transportId = getTransportId();
|
||||
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();
|
||||
@@ -65,7 +64,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||
SimplexPlugin simplexPlugin1 =
|
||||
context.mock(SimplexPlugin.class, "simplexPlugin1");
|
||||
TransportId simplexId1 = getTransportId();
|
||||
TransportId simplexId1 = new TransportId("simplex1");
|
||||
List<SimplexPlugin> simplexPlugins = Arrays.asList(simplexPlugin,
|
||||
simplexPlugin1);
|
||||
TransportConnectionWriter simplexWriter =
|
||||
@@ -73,7 +72,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
|
||||
// Two duplex plugins: one supports polling, the other doesn't
|
||||
DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
|
||||
TransportId duplexId = getTransportId();
|
||||
TransportId duplexId = new TransportId("duplex");
|
||||
DuplexPlugin duplexPlugin1 =
|
||||
context.mock(DuplexPlugin.class, "duplexPlugin1");
|
||||
List<DuplexPlugin> duplexPlugins = Arrays.asList(duplexPlugin,
|
||||
@@ -350,6 +349,7 @@ public class PollerTest extends BrambleMockTestCase {
|
||||
@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();
|
||||
|
||||
@@ -32,9 +32,9 @@ import java.util.Map;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
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.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
@@ -51,7 +51,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
context.mock(ContactGroupFactory.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
|
||||
private final Group localGroup = getGroup(CLIENT_ID);
|
||||
private final Group localGroup = getGroup();
|
||||
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||
private final BdfDictionary fooPropertiesDict = BdfDictionary.of(
|
||||
new BdfEntry("fooKey1", "fooValue1"),
|
||||
@@ -90,8 +90,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
Contact contact1 = getContact(true);
|
||||
Contact contact2 = getContact(true);
|
||||
List<Contact> contacts = Arrays.asList(contact1, contact2);
|
||||
Group contactGroup1 = getGroup(CLIENT_ID);
|
||||
Group contactGroup2 = getGroup(CLIENT_ID);
|
||||
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||
@@ -144,7 +143,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
Group contactGroup = getGroup();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Create the group and share it with the contact
|
||||
@@ -172,7 +171,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testRemovesGroupWhenRemovingContact() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
Group contactGroup = getGroup();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
||||
@@ -307,7 +306,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testStoresRemotePropertiesWithVersion0() throws Exception {
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
Group contactGroup = getGroup();
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Map<TransportId, TransportProperties> properties =
|
||||
new LinkedHashMap<>();
|
||||
@@ -400,9 +399,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
fooPropertiesDict);
|
||||
will(returnValue(fooProperties));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
@@ -421,8 +417,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
Contact contact3 = getContact(true);
|
||||
List<Contact> contacts =
|
||||
Arrays.asList(contact1, contact2, contact3);
|
||||
Group contactGroup2 = getGroup(CLIENT_ID);
|
||||
Group contactGroup3 = getGroup(CLIENT_ID);
|
||||
Group contactGroup2 = getGroup();
|
||||
Group contactGroup3 = getGroup();
|
||||
Map<MessageId, BdfDictionary> messageMetadata3 =
|
||||
new LinkedHashMap<>();
|
||||
// A remote update for another transport should be ignored
|
||||
@@ -470,9 +466,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(messageMetadata3));
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooUpdateId);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
fooPropertiesDict);
|
||||
will(returnValue(fooProperties));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
@@ -508,9 +501,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, updateId);
|
||||
will(returnValue(update));
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
fooPropertiesDict);
|
||||
will(returnValue(fooProperties));
|
||||
// Properties are unchanged so we're done
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
@@ -524,7 +514,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testMergingNewPropertiesCreatesUpdate() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
Group contactGroup = getGroup();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
@@ -559,7 +549,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testMergingUpdatedPropertiesCreatesUpdate() throws Exception {
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Contact contact = getContact(true);
|
||||
Group contactGroup = getGroup(CLIENT_ID);
|
||||
Group contactGroup = getGroup();
|
||||
BdfDictionary oldMetadata = BdfDictionary.of(
|
||||
new BdfEntry("transportId", "foo"),
|
||||
new BdfEntry("version", 1),
|
||||
@@ -571,12 +561,9 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
MessageId contactGroupUpdateId = new MessageId(getRandomId());
|
||||
Map<MessageId, BdfDictionary> contactGroupMessageMetadata =
|
||||
Collections.singletonMap(contactGroupUpdateId, oldMetadata);
|
||||
TransportProperties oldProperties = new TransportProperties();
|
||||
oldProperties.put("fooKey1", "oldFooValue1");
|
||||
BdfDictionary oldPropertiesDict = BdfDictionary.of(
|
||||
BdfList oldUpdate = BdfList.of("foo", 1, BdfDictionary.of(
|
||||
new BdfEntry("fooKey1", "oldFooValue1")
|
||||
);
|
||||
BdfList oldUpdate = BdfList.of("foo", 1, oldPropertiesDict);
|
||||
));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
@@ -587,9 +574,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(localGroupMessageMetadata));
|
||||
oneOf(clientHelper).getMessageAsList(txn, localGroupUpdateId);
|
||||
will(returnValue(oldUpdate));
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
oldPropertiesDict);
|
||||
will(returnValue(oldProperties));
|
||||
// Store the merged properties in the local group, version 2
|
||||
expectStoreMessage(txn, localGroup.getId(), "foo",
|
||||
fooPropertiesDict, 2, true, false);
|
||||
@@ -616,6 +600,12 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
t.mergeLocalProperties(new TransportId("foo"), fooProperties);
|
||||
}
|
||||
|
||||
private Group getGroup() {
|
||||
GroupId g = new GroupId(getRandomId());
|
||||
byte[] descriptor = getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||
return new Group(g, CLIENT_ID, descriptor);
|
||||
}
|
||||
|
||||
private Contact getContact(boolean active) {
|
||||
ContactId c = new ContactId(nextContactId++);
|
||||
return new Contact(c, getAuthor(), localAuthor.getId(),
|
||||
@@ -653,14 +643,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
||||
// Retrieve and parse the latest local properties
|
||||
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
|
||||
will(returnValue(fooUpdate));
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
fooPropertiesDict);
|
||||
will(returnValue(fooProperties));
|
||||
oneOf(clientHelper).getMessageAsList(txn, barVersion3);
|
||||
will(returnValue(barUpdate));
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
barPropertiesDict);
|
||||
will(returnValue(barProperties));
|
||||
}});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,78 +3,80 @@ package org.briarproject.bramble.properties;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
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.api.system.Clock;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TransportPropertyValidatorTest extends BrambleMockTestCase {
|
||||
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
public class TransportPropertyValidatorTest extends BrambleTestCase {
|
||||
|
||||
private final TransportId transportId;
|
||||
private final BdfDictionary bdfDictionary;
|
||||
private final TransportProperties transportProperties;
|
||||
private final Group group;
|
||||
private final Message message;
|
||||
private final TransportPropertyValidator tpv;
|
||||
|
||||
public TransportPropertyValidatorTest() {
|
||||
transportId = getTransportId();
|
||||
bdfDictionary = BdfDictionary.of(new BdfEntry("foo", "bar"));
|
||||
transportProperties = new TransportProperties();
|
||||
transportProperties.put("foo", "bar");
|
||||
transportId = new TransportId("test");
|
||||
bdfDictionary = new BdfDictionary();
|
||||
|
||||
group = getGroup(getClientId());
|
||||
message = getMessage(group.getId());
|
||||
GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
ClientId clientId = new ClientId(StringUtils.getRandomString(5));
|
||||
byte[] descriptor = TestUtils.getRandomBytes(12);
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
|
||||
MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] body = TestUtils.getRandomBytes(123);
|
||||
message = new Message(messageId, groupId, timestamp, body);
|
||||
|
||||
Mockery context = new Mockery();
|
||||
ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||
Clock clock = context.mock(Clock.class);
|
||||
|
||||
tpv = new TransportPropertyValidator(clientHelper, metadataEncoder,
|
||||
clock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateProperMessage() throws IOException {
|
||||
|
||||
BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateTransportProperties(
|
||||
bdfDictionary);
|
||||
will(returnValue(transportProperties));
|
||||
}});
|
||||
BdfDictionary result = tpv.validateMessage(message, group, body)
|
||||
.getDictionary();
|
||||
|
||||
BdfDictionary result =
|
||||
tpv.validateMessage(message, group, body).getDictionary();
|
||||
assertEquals(transportId.getString(), result.getString("transportId"));
|
||||
assertEquals("test", result.getString("transportId"));
|
||||
assertEquals(4, result.getLong("version").longValue());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateWrongVersionValue() throws IOException {
|
||||
|
||||
BdfList body = BdfList.of(transportId.getString(), -1, bdfDictionary);
|
||||
tpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateWrongVersionType() throws IOException {
|
||||
|
||||
BdfList body = BdfList.of(transportId.getString(), bdfDictionary,
|
||||
bdfDictionary);
|
||||
tpv.validateMessage(message, group, body);
|
||||
@@ -82,15 +84,27 @@ public class TransportPropertyValidatorTest extends BrambleMockTestCase {
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateLongTransportId() throws IOException {
|
||||
|
||||
String wrongTransportIdString =
|
||||
getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
|
||||
StringUtils.getRandomString(MAX_TRANSPORT_ID_LENGTH + 1);
|
||||
BdfList body = BdfList.of(wrongTransportIdString, 4, bdfDictionary);
|
||||
tpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateEmptyTransportId() throws IOException {
|
||||
|
||||
BdfList body = BdfList.of("", 4, bdfDictionary);
|
||||
tpv.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateTooManyProperties() throws IOException {
|
||||
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
for (int i = 0; i < MAX_PROPERTIES_PER_TRANSPORT + 1; i++)
|
||||
d.put(String.valueOf(i), i);
|
||||
BdfList body = BdfList.of(transportId.getString(), 4, d);
|
||||
tpv.validateMessage(message, group, body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ import javax.inject.Inject;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
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;
|
||||
@@ -74,13 +73,13 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
component.inject(this);
|
||||
|
||||
contactId = new ContactId(234);
|
||||
transportId = getTransportId();
|
||||
transportId = new TransportId("id");
|
||||
// Create the transport keys
|
||||
tagKey = TestUtils.getSecretKey();
|
||||
headerKey = TestUtils.getSecretKey();
|
||||
streamNumber = 123;
|
||||
// Create a group
|
||||
ClientId clientId = getClientId();
|
||||
ClientId clientId = new ClientId(getRandomString(123));
|
||||
int clientVersion = 1234567890;
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
Group group = groupFactory.createGroup(clientId, clientVersion,
|
||||
|
||||
@@ -21,7 +21,9 @@ import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -36,9 +38,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.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
|
||||
public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -52,12 +51,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Executor dbExecutor = new ImmediateExecutor();
|
||||
private final Executor validationExecutor = new ImmediateExecutor();
|
||||
private final ClientId clientId = getClientId();
|
||||
private final MessageId messageId = new MessageId(getRandomId());
|
||||
private final MessageId messageId1 = new MessageId(getRandomId());
|
||||
private final MessageId messageId2 = new MessageId(getRandomId());
|
||||
private final Group group = getGroup(clientId);
|
||||
private final GroupId groupId = group.getId();
|
||||
private final ClientId clientId =
|
||||
new ClientId(StringUtils.getRandomString(5));
|
||||
private final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
private final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
private final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
private final GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
private final byte[] descriptor = new byte[32];
|
||||
private final Group group = new Group(groupId, clientId, descriptor);
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
private final byte[] raw = new byte[123];
|
||||
private final Message message = new Message(messageId, groupId, timestamp,
|
||||
@@ -99,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);
|
||||
@@ -137,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);
|
||||
@@ -198,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);
|
||||
@@ -226,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);
|
||||
@@ -291,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);
|
||||
@@ -312,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);
|
||||
@@ -327,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);
|
||||
@@ -415,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);
|
||||
@@ -456,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);
|
||||
@@ -486,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);
|
||||
@@ -532,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);
|
||||
@@ -715,8 +716,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testRecursiveInvalidation() throws Exception {
|
||||
MessageId messageId3 = new MessageId(getRandomId());
|
||||
MessageId messageId4 = new MessageId(getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
||||
Map<MessageId, State> twoDependents = new LinkedHashMap<>();
|
||||
twoDependents.put(messageId1, PENDING);
|
||||
twoDependents.put(messageId2, PENDING);
|
||||
@@ -818,8 +819,8 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testPendingDependentsGetDelivered() throws Exception {
|
||||
MessageId messageId3 = new MessageId(getRandomId());
|
||||
MessageId messageId4 = new MessageId(getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
||||
Message message3 = new Message(messageId3, groupId, timestamp,
|
||||
raw);
|
||||
Message message4 = new Message(messageId4, groupId, timestamp,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -16,12 +16,10 @@ import javax.annotation.Nullable;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
|
||||
@Module
|
||||
public class TestPluginConfigModule {
|
||||
|
||||
public static final TransportId TRANSPORT_ID = getTransportId();
|
||||
public static final TransportId TRANSPORT_ID = new TransportId("id");
|
||||
public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
||||
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
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.api.system.Clock;
|
||||
import org.jmock.Expectations;
|
||||
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getClientId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||
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;
|
||||
|
||||
public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
||||
|
||||
@@ -23,27 +21,17 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase {
|
||||
protected final MetadataEncoder metadataEncoder =
|
||||
context.mock(MetadataEncoder.class);
|
||||
protected final Clock clock = context.mock(Clock.class);
|
||||
protected final AuthorFactory authorFactory =
|
||||
context.mock(AuthorFactory.class);
|
||||
|
||||
protected final Group group = getGroup(getClientId());
|
||||
protected final GroupId groupId = group.getId();
|
||||
protected final byte[] descriptor = group.getDescriptor();
|
||||
protected final Message message = getMessage(groupId);
|
||||
protected final MessageId messageId = message.getId();
|
||||
protected final long timestamp = message.getTimestamp();
|
||||
protected final byte[] raw = message.getRaw();
|
||||
protected final Author author = getAuthor();
|
||||
protected final BdfList authorList = BdfList.of(
|
||||
author.getFormatVersion(),
|
||||
author.getName(),
|
||||
author.getPublicKey()
|
||||
);
|
||||
protected final MessageId messageId = new MessageId(getRandomId());
|
||||
protected final GroupId groupId = new GroupId(getRandomId());
|
||||
protected final long timestamp = 1234567890 * 1000L;
|
||||
protected final byte[] raw = getRandomBytes(123);
|
||||
protected final Message message =
|
||||
new Message(messageId, groupId, timestamp, raw);
|
||||
protected final ClientId clientId = new ClientId(getRandomString(123));
|
||||
protected final byte[] descriptor = getRandomBytes(123);
|
||||
protected final Group group = new Group(groupId, clientId, descriptor);
|
||||
|
||||
protected void expectParseAuthor(BdfList authorList, Author author)
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).parseAndValidateAuthor(authorList);
|
||||
will(returnValue(author));
|
||||
}});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,51 +12,51 @@ import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.concurrent.DeterministicExecutor;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
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.test.TestUtils.getTransportId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
public class KeyManagerImplTest extends BrambleTestCase {
|
||||
|
||||
private final Mockery context = new Mockery();
|
||||
private final KeyManagerImpl keyManager;
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final PluginConfig pluginConfig = context.mock(PluginConfig.class);
|
||||
private final TransportKeyManagerFactory transportKeyManagerFactory =
|
||||
context.mock(TransportKeyManagerFactory.class);
|
||||
private final TransportKeyManager transportKeyManager =
|
||||
context.mock(TransportKeyManager.class);
|
||||
|
||||
private final DeterministicExecutor executor = new DeterministicExecutor();
|
||||
private final Transaction txn = new Transaction(null, false);
|
||||
private final ContactId contactId = new ContactId(123);
|
||||
private final ContactId inactiveContactId = new ContactId(234);
|
||||
private final KeySetId keySetId = new KeySetId(345);
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final TransportId unknownTransportId = getTransportId();
|
||||
private final ContactId contactId = new ContactId(42);
|
||||
private final ContactId inactiveContactId = new ContactId(43);
|
||||
private final TransportId transportId = new TransportId("tId");
|
||||
private final TransportId unknownTransportId = new TransportId("id");
|
||||
private final StreamContext streamContext =
|
||||
new StreamContext(contactId, transportId, getSecretKey(),
|
||||
getSecretKey(), 1);
|
||||
private final byte[] tag = getRandomBytes(TAG_LENGTH);
|
||||
|
||||
private final KeyManagerImpl keyManager = new KeyManagerImpl(db, executor,
|
||||
pluginConfig, transportKeyManagerFactory);
|
||||
public KeyManagerImplTest() {
|
||||
keyManager = new KeyManagerImpl(db, executor, pluginConfig,
|
||||
transportKeyManagerFactory);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void testStartService() throws Exception {
|
||||
@@ -70,8 +70,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
true, false));
|
||||
SimplexPluginFactory pluginFactory =
|
||||
context.mock(SimplexPluginFactory.class);
|
||||
Collection<SimplexPluginFactory> factories =
|
||||
singletonList(pluginFactory);
|
||||
Collection<SimplexPluginFactory> factories = Collections
|
||||
.singletonList(pluginFactory);
|
||||
int maxLatency = 1337;
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
@@ -110,22 +110,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
|
||||
keyManager.addContact(txn, contactId, secretKey, timestamp, alice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddUnboundKeys() throws Exception {
|
||||
SecretKey secretKey = getSecretKey();
|
||||
long timestamp = System.currentTimeMillis();
|
||||
boolean alice = new Random().nextBoolean();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportKeyManager).addUnboundKeys(txn, secretKey,
|
||||
timestamp, alice);
|
||||
will(returnValue(keySetId));
|
||||
}});
|
||||
|
||||
assertEquals(singletonMap(transportId, keySetId),
|
||||
keyManager.addUnboundKeys(txn, secretKey, timestamp, alice));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -153,6 +138,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
assertEquals(streamContext,
|
||||
keyManager.getStreamContext(contactId, transportId));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -175,6 +161,7 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
assertEquals(streamContext,
|
||||
keyManager.getStreamContext(transportId, tag));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -188,6 +175,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
keyManager.eventOccurred(event);
|
||||
executor.runUntilIdle();
|
||||
assertEquals(null, keyManager.getStreamContext(contactId, transportId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -207,5 +196,8 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||
keyManager.eventOccurred(event);
|
||||
assertEquals(streamContext,
|
||||
keyManager.getStreamContext(inactiveContactId, transportId));
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.KeySet;
|
||||
import org.briarproject.bramble.api.transport.KeySetId;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
@@ -24,25 +22,23 @@ import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@@ -54,14 +50,11 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
context.mock(ScheduledExecutorService.class);
|
||||
private final Clock clock = context.mock(Clock.class);
|
||||
|
||||
private final TransportId transportId = getTransportId();
|
||||
private final TransportId transportId = new TransportId("id");
|
||||
private final long maxLatency = 30 * 1000; // 30 seconds
|
||||
private final long rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||
private final ContactId contactId = new ContactId(123);
|
||||
private final ContactId contactId1 = new ContactId(234);
|
||||
private final KeySetId keySetId = new KeySetId(345);
|
||||
private final KeySetId keySetId1 = new KeySetId(456);
|
||||
private final KeySetId keySetId2 = new KeySetId(567);
|
||||
private final SecretKey tagKey = TestUtils.getSecretKey();
|
||||
private final SecretKey headerKey = TestUtils.getSecretKey();
|
||||
private final SecretKey masterKey = TestUtils.getSecretKey();
|
||||
@@ -69,16 +62,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testKeysAreRotatedAtStartup() throws Exception {
|
||||
TransportKeys shouldRotate = createTransportKeys(900, 0, true);
|
||||
TransportKeys shouldNotRotate = createTransportKeys(1000, 0, true);
|
||||
TransportKeys shouldRotate1 = createTransportKeys(999, 0, false);
|
||||
Collection<KeySet> loaded = asList(
|
||||
new KeySet(keySetId, contactId, shouldRotate),
|
||||
new KeySet(keySetId1, contactId1, shouldNotRotate),
|
||||
new KeySet(keySetId2, null, shouldRotate1)
|
||||
);
|
||||
TransportKeys rotated = createTransportKeys(1000, 0, true);
|
||||
TransportKeys rotated1 = createTransportKeys(1000, 0, false);
|
||||
Map<ContactId, TransportKeys> loaded = new LinkedHashMap<>();
|
||||
TransportKeys shouldRotate = createTransportKeys(900, 0);
|
||||
TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
|
||||
loaded.put(contactId, shouldRotate);
|
||||
loaded.put(contactId1, shouldNotRotate);
|
||||
TransportKeys rotated = createTransportKeys(1000, 0);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
@@ -93,8 +82,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(rotated));
|
||||
oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000);
|
||||
will(returnValue(shouldNotRotate));
|
||||
oneOf(transportCrypto).rotateTransportKeys(shouldRotate1, 1000);
|
||||
will(returnValue(rotated1));
|
||||
// Encode the tags (3 sets per contact)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(6).of(transportCrypto).encodeTag(
|
||||
@@ -103,10 +90,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Save the keys that were rotated
|
||||
oneOf(db).updateTransportKeys(txn, asList(
|
||||
new KeySet(keySetId, contactId, rotated),
|
||||
new KeySet(keySetId2, null, rotated1))
|
||||
);
|
||||
oneOf(db).updateTransportKeys(txn,
|
||||
Collections.singletonMap(contactId, rotated));
|
||||
// Schedule key rotation at the start of the next rotation period
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with(rotationPeriodLength - 1), with(MILLISECONDS));
|
||||
@@ -116,19 +101,18 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
maxLatency);
|
||||
transportKeyManager.start(txn);
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreRotatedWhenAddingContact() throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(999, 0, true);
|
||||
TransportKeys rotated = createTransportKeys(1000, 0, true);
|
||||
TransportKeys transportKeys = createTransportKeys(999, 0);
|
||||
TransportKeys rotated = createTransportKeys(1000, 0);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
999, alice, true);
|
||||
999, alice);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (1 ms after start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
@@ -145,7 +129,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, contactId, rotated);
|
||||
will(returnValue(keySetId));
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
@@ -155,39 +138,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
long timestamp = rotationPeriodLength * 1000 - 1;
|
||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||
alice);
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeysAreRotatedWhenAddingUnboundKeys() throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(999, 0, false);
|
||||
TransportKeys rotated = createTransportKeys(1000, 0, false);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
999, alice, false);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (1 ms after start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(rotationPeriodLength * 1000 + 1));
|
||||
// Rotate the transport keys
|
||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||
will(returnValue(rotated));
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, null, rotated);
|
||||
will(returnValue(keySetId));
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
maxLatency);
|
||||
// The timestamp is 1 ms before the start of rotation period 1000
|
||||
long timestamp = rotationPeriodLength * 1000 - 1;
|
||||
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||
masterKey, timestamp, alice));
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -199,7 +149,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
maxLatency);
|
||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -208,10 +157,29 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
boolean alice = random.nextBoolean();
|
||||
// The stream counter has been exhausted
|
||||
TransportKeys transportKeys = createTransportKeys(1000,
|
||||
MAX_32_BIT_UNSIGNED + 1, true);
|
||||
MAX_32_BIT_UNSIGNED + 1);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
expectAddContactNoRotation(alice, transportKeys, txn);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
1000, alice);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (the start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(rotationPeriodLength * 1000));
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(transportCrypto).encodeTag(
|
||||
with(any(byte[].class)), with(tagKey),
|
||||
with(PROTOCOL_VERSION), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||
will(returnValue(transportKeys));
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
@@ -220,7 +188,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
long timestamp = rotationPeriodLength * 1000;
|
||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||
alice);
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||
}
|
||||
|
||||
@@ -229,14 +196,30 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
boolean alice = random.nextBoolean();
|
||||
// The stream counter can be used one more time before being exhausted
|
||||
TransportKeys transportKeys = createTransportKeys(1000,
|
||||
MAX_32_BIT_UNSIGNED, true);
|
||||
MAX_32_BIT_UNSIGNED);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
expectAddContactNoRotation(alice, transportKeys, txn);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
1000, alice);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (the start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(rotationPeriodLength * 1000));
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(transportCrypto).encodeTag(
|
||||
with(any(byte[].class)), with(tagKey),
|
||||
with(PROTOCOL_VERSION), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||
will(returnValue(transportKeys));
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||
// Increment the stream counter
|
||||
oneOf(db).incrementStreamCounter(txn, transportId, keySetId);
|
||||
oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
@@ -247,7 +230,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||
alice);
|
||||
// The first request should return a stream context
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
StreamContext ctx = transportKeyManager.getStreamContext(txn,
|
||||
contactId);
|
||||
assertNotNull(ctx);
|
||||
@@ -257,7 +239,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
assertEquals(headerKey, ctx.getHeaderKey());
|
||||
assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
|
||||
// The second request should return null, the counter is exhausted
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||
}
|
||||
|
||||
@@ -265,10 +246,29 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
public void testIncomingStreamContextIsNullIfTagIsNotFound()
|
||||
throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
expectAddContactNoRotation(alice, transportKeys, txn);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
1000, alice);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (the start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(rotationPeriodLength * 1000));
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(transportCrypto).encodeTag(
|
||||
with(any(byte[].class)), with(tagKey),
|
||||
with(PROTOCOL_VERSION), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||
will(returnValue(transportKeys));
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
@@ -277,8 +277,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
long timestamp = rotationPeriodLength * 1000;
|
||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||
alice);
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
// The tag should not be recognised
|
||||
assertNull(transportKeyManager.getStreamContext(txn,
|
||||
new byte[TAG_LENGTH]));
|
||||
}
|
||||
@@ -286,15 +284,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testTagIsNotRecognisedTwice() throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
||||
// Keep a copy of the tags
|
||||
List<byte[]> tags = new ArrayList<>();
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
1000, alice, true);
|
||||
1000, alice);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (the start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
@@ -311,14 +308,13 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(transportKeys));
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||
will(returnValue(keySetId));
|
||||
// Encode a new tag after sliding the window
|
||||
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION),
|
||||
with((long) REORDERING_WINDOW_SIZE));
|
||||
will(new EncodeTagAction(tags));
|
||||
// Save the reordering window (previous rotation period, base 1)
|
||||
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
|
||||
oneOf(db).setReorderingWindow(txn, contactId, transportId, 999,
|
||||
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||
}});
|
||||
|
||||
@@ -329,7 +325,6 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
long timestamp = rotationPeriodLength * 1000;
|
||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||
alice);
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
// Use the first tag (previous rotation period, stream number 0)
|
||||
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
|
||||
byte[] tag = tags.get(0);
|
||||
@@ -349,10 +344,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testKeysAreRotatedToCurrentPeriod() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, true);
|
||||
Collection<KeySet> loaded =
|
||||
singletonList(new KeySet(keySetId, contactId, transportKeys));
|
||||
TransportKeys rotated = createTransportKeys(1001, 0, true);
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
||||
Map<ContactId, TransportKeys> loaded =
|
||||
Collections.singletonMap(contactId, transportKeys);
|
||||
TransportKeys rotated = createTransportKeys(1001, 0);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
Transaction txn1 = new Transaction(null, false);
|
||||
|
||||
@@ -398,7 +393,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
// Save the keys that were rotated
|
||||
oneOf(db).updateTransportKeys(txn1,
|
||||
singletonList(new KeySet(keySetId, contactId, rotated)));
|
||||
Collections.singletonMap(contactId, rotated));
|
||||
// Schedule key rotation at the start of the next rotation period
|
||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||
with(rotationPeriodLength), with(MILLISECONDS));
|
||||
@@ -411,197 +406,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
maxLatency);
|
||||
transportKeyManager.start(txn);
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingAndActivatingKeys() throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, false);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// When the keys are bound, encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(transportCrypto).encodeTag(
|
||||
with(any(byte[].class)), with(tagKey),
|
||||
with(PROTOCOL_VERSION), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Save the key binding
|
||||
oneOf(db).bindTransportKeys(txn, contactId, transportId, keySetId);
|
||||
// Activate the keys
|
||||
oneOf(db).setTransportKeysActive(txn, transportId, keySetId);
|
||||
// Increment the stream counter
|
||||
oneOf(db).incrementStreamCounter(txn, transportId, keySetId);
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
maxLatency);
|
||||
// The timestamp is at the start of rotation period 1000
|
||||
long timestamp = rotationPeriodLength * 1000;
|
||||
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||
masterKey, timestamp, alice));
|
||||
// The keys are unbound so no stream context should be returned
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||
transportKeyManager.bindKeys(txn, contactId, keySetId);
|
||||
// The keys are inactive so no stream context should be returned
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||
transportKeyManager.activateKeys(txn, keySetId);
|
||||
// The keys are active so a stream context should be returned
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
StreamContext ctx = transportKeyManager.getStreamContext(txn,
|
||||
contactId);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertEquals(tagKey, ctx.getTagKey());
|
||||
assertEquals(headerKey, ctx.getHeaderKey());
|
||||
assertEquals(0L, ctx.getStreamNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecognisingTagActivatesOutgoingKeys() throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, false);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
// Keep a copy of the tags
|
||||
List<byte[]> tags = new ArrayList<>();
|
||||
|
||||
expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// When the keys are bound, encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(transportCrypto).encodeTag(
|
||||
with(any(byte[].class)), with(tagKey),
|
||||
with(PROTOCOL_VERSION), with(i));
|
||||
will(new EncodeTagAction(tags));
|
||||
}
|
||||
// Save the key binding
|
||||
oneOf(db).bindTransportKeys(txn, contactId, transportId, keySetId);
|
||||
// Encode a new tag after sliding the window
|
||||
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
||||
with(tagKey), with(PROTOCOL_VERSION),
|
||||
with((long) REORDERING_WINDOW_SIZE));
|
||||
will(new EncodeTagAction(tags));
|
||||
// Save the reordering window (previous rotation period, base 1)
|
||||
oneOf(db).setReorderingWindow(txn, keySetId, transportId, 999,
|
||||
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||
// Activate the keys
|
||||
oneOf(db).setTransportKeysActive(txn, transportId, keySetId);
|
||||
// Increment the stream counter
|
||||
oneOf(db).incrementStreamCounter(txn, transportId, keySetId);
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
maxLatency);
|
||||
// The timestamp is at the start of rotation period 1000
|
||||
long timestamp = rotationPeriodLength * 1000;
|
||||
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||
masterKey, timestamp, alice));
|
||||
transportKeyManager.bindKeys(txn, contactId, keySetId);
|
||||
// The keys are inactive so no stream context should be returned
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||
// Recognising an incoming tag should activate the outgoing keys
|
||||
assertEquals(REORDERING_WINDOW_SIZE * 3, tags.size());
|
||||
byte[] tag = tags.get(0);
|
||||
StreamContext ctx = transportKeyManager.getStreamContext(txn, tag);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertEquals(tagKey, ctx.getTagKey());
|
||||
assertEquals(headerKey, ctx.getHeaderKey());
|
||||
assertEquals(0L, ctx.getStreamNumber());
|
||||
// The keys are active so a stream context should be returned
|
||||
assertTrue(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
ctx = transportKeyManager.getStreamContext(txn, contactId);
|
||||
assertNotNull(ctx);
|
||||
assertEquals(contactId, ctx.getContactId());
|
||||
assertEquals(transportId, ctx.getTransportId());
|
||||
assertEquals(tagKey, ctx.getTagKey());
|
||||
assertEquals(headerKey, ctx.getHeaderKey());
|
||||
assertEquals(0L, ctx.getStreamNumber());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovingUnboundKeys() throws Exception {
|
||||
boolean alice = random.nextBoolean();
|
||||
TransportKeys transportKeys = createTransportKeys(1000, 0, false);
|
||||
Transaction txn = new Transaction(null, false);
|
||||
|
||||
expectAddUnboundKeysNoRotation(alice, transportKeys, txn);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// Remove the unbound keys
|
||||
oneOf(db).removeTransportKeys(txn, transportId, keySetId);
|
||||
}});
|
||||
|
||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
||||
maxLatency);
|
||||
// The timestamp is at the start of rotation period 1000
|
||||
long timestamp = rotationPeriodLength * 1000;
|
||||
assertEquals(keySetId, transportKeyManager.addUnboundKeys(txn,
|
||||
masterKey, timestamp, alice));
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
transportKeyManager.removeKeys(txn, keySetId);
|
||||
assertFalse(transportKeyManager.canSendOutgoingStreams(contactId));
|
||||
}
|
||||
|
||||
private void expectAddContactNoRotation(boolean alice,
|
||||
TransportKeys transportKeys, Transaction txn) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
1000, alice, true);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (the start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(rotationPeriodLength * 1000));
|
||||
// Encode the tags (3 sets)
|
||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||
exactly(3).of(transportCrypto).encodeTag(
|
||||
with(any(byte[].class)), with(tagKey),
|
||||
with(PROTOCOL_VERSION), with(i));
|
||||
will(new EncodeTagAction());
|
||||
}
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||
will(returnValue(transportKeys));
|
||||
// Save the keys
|
||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||
will(returnValue(keySetId));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectAddUnboundKeysNoRotation(boolean alice,
|
||||
TransportKeys transportKeys, Transaction txn) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
||||
1000, alice, false);
|
||||
will(returnValue(transportKeys));
|
||||
// Get the current time (the start of rotation period 1000)
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(rotationPeriodLength * 1000));
|
||||
// Rotate the transport keys (the keys are unaffected)
|
||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
||||
will(returnValue(transportKeys));
|
||||
// Save the unbound keys
|
||||
oneOf(db).addTransportKeys(txn, null, transportKeys);
|
||||
will(returnValue(keySetId));
|
||||
}});
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys(long rotationPeriod,
|
||||
long streamCounter, boolean active) {
|
||||
long streamCounter) {
|
||||
IncomingKeys inPrev = new IncomingKeys(tagKey, headerKey,
|
||||
rotationPeriod - 1);
|
||||
IncomingKeys inCurr = new IncomingKeys(tagKey, headerKey,
|
||||
@@ -609,7 +417,7 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
||||
IncomingKeys inNext = new IncomingKeys(tagKey, headerKey,
|
||||
rotationPeriod + 1);
|
||||
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
|
||||
rotationPeriod, streamCounter, active);
|
||||
rotationPeriod, streamCounter);
|
||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
JavaBluetoothPlugin(BluetoothConnectionManager connectionManager,
|
||||
Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
super(connectionManager, ioExecutor, secureRandom, backoff, callback,
|
||||
maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,6 +112,6 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new JavaBluetoothTransportConnection(this, s);
|
||||
return new JavaBluetoothTransportConnection(this, connectionManager, s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,12 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
@Override
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
BluetoothConnectionManager connectionManager =
|
||||
new BluetoothConnectionManagerImpl();
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionManager,
|
||||
ioExecutor, secureRandom, backoff, callback, MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,15 @@ import javax.microedition.io.StreamConnection;
|
||||
class JavaBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final BluetoothConnectionManager connectionManager;
|
||||
private final StreamConnection stream;
|
||||
|
||||
JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||
JavaBluetoothTransportConnection(Plugin plugin,
|
||||
BluetoothConnectionManager connectionManager,
|
||||
StreamConnection stream) {
|
||||
super(plugin);
|
||||
this.stream = stream;
|
||||
this.connectionManager = connectionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,6 +37,10 @@ class JavaBluetoothTransportConnection
|
||||
|
||||
@Override
|
||||
protected void closeConnection(boolean exception) throws IOException {
|
||||
stream.close();
|
||||
try {
|
||||
stream.close();
|
||||
} finally {
|
||||
connectionManager.connectionClosed(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -352,16 +347,6 @@
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.test.TestDataActivity"
|
||||
android:label="Create test data"
|
||||
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.panic.PanicPreferencesActivity"
|
||||
android:label="@string/panic_setting"
|
||||
@@ -384,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>
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ 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.Multiset;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
@@ -62,7 +61,6 @@ 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;
|
||||
@@ -91,6 +89,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private static final int BLOG_POST_NOTIFICATION_ID = 6;
|
||||
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7;
|
||||
|
||||
// Channel IDs
|
||||
private static final String CONTACT_CHANNEL_ID = "contacts";
|
||||
private static final String GROUP_CHANNEL_ID = "groups";
|
||||
private static final String FORUM_CHANNEL_ID = "forums";
|
||||
private static final String BLOG_CHANNEL_ID = "blogs";
|
||||
|
||||
private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
|
||||
|
||||
private static final Logger LOG =
|
||||
@@ -166,15 +170,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@TargetApi(26)
|
||||
private void createNotificationChannel(String channelId,
|
||||
@StringRes int name) {
|
||||
NotificationChannel nc =
|
||||
notificationManager.createNotificationChannel(
|
||||
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);
|
||||
IMPORTANCE_DEFAULT));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -94,7 +94,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;
|
||||
|
||||
@@ -4,11 +4,8 @@ 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.IBinder;
|
||||
@@ -20,26 +17,19 @@ 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;
|
||||
@@ -71,9 +61,6 @@ public class BriarService extends Service {
|
||||
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,7 +88,6 @@ public class BriarService extends Service {
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create notification channels
|
||||
if (SDK_INT >= 26) {
|
||||
NotificationManager nm = (NotificationManager)
|
||||
@@ -153,19 +139,6 @@ public class BriarService extends Service {
|
||||
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);
|
||||
}
|
||||
|
||||
private void showStartupFailureNotification(StartResult result) {
|
||||
@@ -210,7 +183,6 @@ 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(() -> {
|
||||
if (started) lifecycleManager.stopServices();
|
||||
@@ -221,48 +193,7 @@ public class BriarService extends Service {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user