mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
163 Commits
network-lo
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9be6d6c00f | ||
|
|
d5643d8e5d | ||
|
|
30bfa91fc4 | ||
|
|
c3e4742bfe | ||
|
|
d4b87983e8 | ||
|
|
eed1439745 | ||
|
|
4ba3708931 | ||
|
|
215c62ed23 | ||
|
|
4100daaa47 | ||
|
|
6fa6ceb5ee | ||
|
|
cc2791c37f | ||
|
|
20a131bec5 | ||
|
|
edee90dbe2 | ||
|
|
fd78139a5a | ||
|
|
41242ef369 | ||
|
|
c55bef95ce | ||
|
|
fb6b487212 | ||
|
|
97f40bd20b | ||
|
|
36b191e9d4 | ||
|
|
ebaa50b101 | ||
|
|
4c5331888a | ||
|
|
c5efb6e16d | ||
|
|
522cba6ac3 | ||
|
|
f25fbc5b94 | ||
|
|
57a6c8cb3a | ||
|
|
6942a368d4 | ||
|
|
c4e9b6f2ab | ||
|
|
05deaf42e3 | ||
|
|
2e570ba50d | ||
|
|
cadb17987c | ||
|
|
e76f114a72 | ||
|
|
cb11b55a9a | ||
|
|
f4c5855dd8 | ||
|
|
be309057cd | ||
|
|
cf396c2ce2 | ||
|
|
a9f77f0f90 | ||
|
|
cc6fed0298 | ||
|
|
66137d4cfa | ||
|
|
114044ee5f | ||
|
|
1197d65d8d | ||
|
|
85c11f8e1f | ||
|
|
8c00f2417b | ||
|
|
a38f39207f | ||
|
|
b7874365a3 | ||
|
|
196caa7b45 | ||
|
|
3fd6ce2313 | ||
|
|
c42852cde2 | ||
|
|
a38b0a8527 | ||
|
|
79d6fd28de | ||
|
|
68132d893b | ||
|
|
6b011d2a7d | ||
|
|
d7492df81c | ||
|
|
ebf73716bb | ||
|
|
6e42377b74 | ||
|
|
e8f33c0e6e | ||
|
|
5f6af4e40f | ||
|
|
55a329a879 | ||
|
|
23f0864d8b | ||
|
|
c0dfe3e85a | ||
|
|
31b69577e8 | ||
|
|
99dba69c87 | ||
|
|
44f5a9db1e | ||
|
|
80a9689316 | ||
|
|
337f7e7b8f | ||
|
|
f8f98ed95d | ||
|
|
bd5504de26 | ||
|
|
0e04044ebb | ||
|
|
0a5d408686 | ||
|
|
f94db28035 | ||
|
|
b291fcd2cd | ||
|
|
94a6137a42 | ||
|
|
72e9a9d807 | ||
|
|
a9b678df32 | ||
|
|
f81ef30b47 | ||
|
|
1bc29fec06 | ||
|
|
61b216f572 | ||
|
|
d57102ed90 | ||
|
|
e1fae7ad95 | ||
|
|
672a52b2e5 | ||
|
|
155c6a5613 | ||
|
|
218b2f7ff9 | ||
|
|
f78f065204 | ||
|
|
0217c205a1 | ||
|
|
615f527270 | ||
|
|
b9cf1da861 | ||
|
|
69c34adae3 | ||
|
|
fe213d46e3 | ||
|
|
ac1bfcae60 | ||
|
|
9efb6ab38f | ||
|
|
b30c2a8033 | ||
|
|
575847cb36 | ||
|
|
951605151f | ||
|
|
05735e7a48 | ||
|
|
f835e82653 | ||
|
|
d074e4a3d6 | ||
|
|
87a92c9ab6 | ||
|
|
89cc769dea | ||
|
|
fcdc6ebafd | ||
|
|
215d236c2c | ||
|
|
e1b8b271e3 | ||
|
|
9379990480 | ||
|
|
c7718db419 | ||
|
|
9196169561 | ||
|
|
ff9971b728 | ||
|
|
8decc73f4d | ||
|
|
d23fc2cbda | ||
|
|
58d1707467 | ||
|
|
b08b2c691c | ||
|
|
284e3a2e86 | ||
|
|
0823934e28 | ||
|
|
95b9b3a3c6 | ||
|
|
ede390b897 | ||
|
|
5b790130d4 | ||
|
|
ff44edf714 | ||
|
|
60dffd0998 | ||
|
|
46dae59444 | ||
|
|
e385d58148 | ||
|
|
b20626935e | ||
|
|
8e9fc3b338 | ||
|
|
c3a70fe58d | ||
|
|
ddfaddccdc | ||
|
|
4a892acdd5 | ||
|
|
bdb518ff09 | ||
|
|
8ebced9481 | ||
|
|
b81058d6da | ||
|
|
f7c2f86499 | ||
|
|
798b871cc9 | ||
|
|
6787d29f11 | ||
|
|
57e6f2ea9c | ||
|
|
0a802bbe0b | ||
|
|
17fe358fd9 | ||
|
|
5bd2092a03 | ||
|
|
cb8f89db53 | ||
|
|
bb2f94d5eb | ||
|
|
78f2d48bc4 | ||
|
|
309c7a4668 | ||
|
|
750f2b1b75 | ||
|
|
e7b2fe1906 | ||
|
|
bfd22cfced | ||
|
|
ea0223ef1e | ||
|
|
38b739442c | ||
|
|
25f9ab7c33 | ||
|
|
e0a1fa559d | ||
|
|
196cf15ef2 | ||
|
|
6ff0f317a5 | ||
|
|
5a3f47d72c | ||
|
|
7e784c6be1 | ||
|
|
3ee212f3ab | ||
|
|
ee942790d3 | ||
|
|
2d740675c7 | ||
|
|
e4f3960ce0 | ||
|
|
fef916991b | ||
|
|
3fa38d3b28 | ||
|
|
48c41f77c7 | ||
|
|
c3bf82c5b2 | ||
|
|
74fe36c46e | ||
|
|
0d5d1f4cb2 | ||
|
|
2c4d5680a6 | ||
|
|
9e2e0585c5 | ||
|
|
2367e6c481 | ||
|
|
ff8b38f7e8 | ||
|
|
f609ad1a92 | ||
|
|
a0c88da1ac |
@@ -1,6 +1,8 @@
|
|||||||
import de.undercouch.gradle.tasks.download.Download
|
import de.undercouch.gradle.tasks.download.Download
|
||||||
import de.undercouch.gradle.tasks.download.Verify
|
import de.undercouch.gradle.tasks.download.Verify
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
apply plugin: 'de.undercouch.download'
|
apply plugin: 'de.undercouch.download'
|
||||||
@@ -12,8 +14,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1700
|
versionCode 10000
|
||||||
versionName "0.17.0"
|
versionName "1.0.0"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,30 +69,68 @@ def torBinaries = [
|
|||||||
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||||
]
|
]
|
||||||
|
|
||||||
def downloadBinary(name) {
|
def verifyOrDeleteBinary(name, chksum, alreadyVerified) {
|
||||||
return tasks.create("downloadBinary${name}", Download) {
|
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)]) {
|
||||||
src "${torDownloadUrl}${name}.zip"
|
src "${torDownloadUrl}${name}.zip"
|
||||||
.replace('tor_', "tor-${torVersion}-")
|
.replace('tor_', "tor-${torVersion}-")
|
||||||
.replace('geoip', "geoip-${geoipVersion}")
|
.replace('geoip', "geoip-${geoipVersion}")
|
||||||
.replaceAll('_', '-')
|
.replaceAll('_', '-')
|
||||||
dest "${torBinaryDir}/${name}.zip"
|
dest "${torBinaryDir}/${name}.zip"
|
||||||
onlyIfNewer true
|
onlyIf {
|
||||||
|
!dest.exists()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def verifyBinary(name, chksum) {
|
def verifyBinary(name, chksum) {
|
||||||
|
boolean[] alreadyVerified = [false]
|
||||||
return tasks.create([
|
return tasks.create([
|
||||||
name : "verifyBinary${name}",
|
name : "verifyBinary${name}",
|
||||||
type : Verify,
|
type : Verify,
|
||||||
dependsOn: downloadBinary(name)]) {
|
dependsOn: downloadBinary(name, chksum, alreadyVerified)]) {
|
||||||
src "${torBinaryDir}/${name}.zip"
|
src "${torBinaryDir}/${name}.zip"
|
||||||
algorithm 'SHA-256'
|
algorithm 'SHA-256'
|
||||||
checksum chksum
|
checksum chksum
|
||||||
|
onlyIf {
|
||||||
|
!alreadyVerified[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
project.afterEvaluate {
|
project.afterEvaluate {
|
||||||
torBinaries.every { key, value ->
|
torBinaries.every { name, checksum ->
|
||||||
preBuild.dependsOn.add(verifyBinary(key, value))
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class AndroidPluginModule {
|
|||||||
appContext, locationUtils, reporter, eventBus,
|
appContext, locationUtils, reporter, eventBus,
|
||||||
torSocketFactory, backoffFactory);
|
torSocketFactory, backoffFactory);
|
||||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||||
backoffFactory, appContext);
|
scheduler, backoffFactory, appContext);
|
||||||
Collection<DuplexPluginFactory> duplex =
|
Collection<DuplexPluginFactory> duplex =
|
||||||
Arrays.asList(bluetooth, tor, lan);
|
Arrays.asList(bluetooth, tor, lan);
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -55,10 +55,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
// Non-null if the plugin started successfully
|
// Non-null if the plugin started successfully
|
||||||
private volatile BluetoothAdapter adapter = null;
|
private volatile BluetoothAdapter adapter = null;
|
||||||
|
|
||||||
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||||
|
Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||||
DuplexPluginCallback callback, int maxLatency) {
|
DuplexPluginCallback callback, int maxLatency) {
|
||||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
|
||||||
|
maxLatency);
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
}
|
}
|
||||||
@@ -154,7 +156,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||||
return new AndroidBluetoothTransportConnection(this, s);
|
return new AndroidBluetoothTransportConnection(this,
|
||||||
|
connectionLimiter, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -59,11 +59,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||||
|
BluetoothConnectionLimiter connectionLimiter =
|
||||||
|
new BluetoothConnectionLimiterImpl();
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
||||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
connectionLimiter, ioExecutor, androidExecutor, appContext,
|
||||||
MAX_LATENCY);
|
secureRandom, backoff, callback, MAX_LATENCY);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ import java.io.OutputStream;
|
|||||||
class AndroidBluetoothTransportConnection
|
class AndroidBluetoothTransportConnection
|
||||||
extends AbstractDuplexTransportConnection {
|
extends AbstractDuplexTransportConnection {
|
||||||
|
|
||||||
|
private final BluetoothConnectionLimiter connectionManager;
|
||||||
private final BluetoothSocket socket;
|
private final BluetoothSocket socket;
|
||||||
|
|
||||||
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
AndroidBluetoothTransportConnection(Plugin plugin,
|
||||||
|
BluetoothConnectionLimiter connectionManager,
|
||||||
|
BluetoothSocket socket) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +37,10 @@ class AndroidBluetoothTransportConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void closeConnection(boolean exception) throws IOException {
|
protected void closeConnection(boolean exception) throws IOException {
|
||||||
socket.close();
|
try {
|
||||||
|
socket.close();
|
||||||
|
} finally {
|
||||||
|
connectionManager.connectionClosed(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,43 +5,84 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.Network;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.Bundle;
|
import android.net.wifi.WifiInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
|
import static android.content.Context.WIFI_SERVICE;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
|
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
|
||||||
import static java.util.logging.Level.INFO;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static org.briarproject.bramble.util.AndroidUtils.logNetworkState;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||||
|
|
||||||
private static final String WIFI_AP_STATE_ACTION =
|
// See android.net.wifi.WifiManager
|
||||||
|
private static final String WIFI_AP_STATE_CHANGED_ACTION =
|
||||||
"android.net.wifi.WIFI_AP_STATE_CHANGED";
|
"android.net.wifi.WIFI_AP_STATE_CHANGED";
|
||||||
|
private static final int WIFI_AP_STATE_ENABLED = 13;
|
||||||
|
|
||||||
|
private static final byte[] WIFI_AP_ADDRESS_BYTES =
|
||||||
|
{(byte) 192, (byte) 168, 43, 1};
|
||||||
|
private static final InetAddress WIFI_AP_ADDRESS;
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// Should only be thrown if the address has an illegal length
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
|
private final ConnectivityManager connectivityManager;
|
||||||
|
@Nullable
|
||||||
|
private final WifiManager wifiManager;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||||
|
private volatile SocketFactory socketFactory;
|
||||||
|
|
||||||
AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff,
|
AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||||
Context appContext, DuplexPluginCallback callback, int maxLatency,
|
Backoff backoff, Context appContext, DuplexPluginCallback callback,
|
||||||
int maxIdleTime) {
|
int maxLatency, int maxIdleTime) {
|
||||||
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
|
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
|
||||||
|
this.scheduler = scheduler;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
|
ConnectivityManager connectivityManager = (ConnectivityManager)
|
||||||
|
appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||||
|
if (connectivityManager == null) throw new AssertionError();
|
||||||
|
this.connectivityManager = connectivityManager;
|
||||||
|
wifiManager = (WifiManager) appContext.getApplicationContext()
|
||||||
|
.getSystemService(WIFI_SERVICE);
|
||||||
|
socketFactory = SocketFactory.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -52,9 +93,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
networkStateReceiver = new NetworkStateReceiver();
|
networkStateReceiver = new NetworkStateReceiver();
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(CONNECTIVITY_ACTION);
|
filter.addAction(CONNECTIVITY_ACTION);
|
||||||
filter.addAction(WIFI_AP_STATE_ACTION);
|
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
|
||||||
appContext.registerReceiver(networkStateReceiver, filter);
|
appContext.registerReceiver(networkStateReceiver, filter);
|
||||||
if (LOG.isLoggable(INFO)) logNetworkState(appContext, LOG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,38 +105,92 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
tryToClose(socket);
|
tryToClose(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Socket createSocket() throws IOException {
|
||||||
|
return socketFactory.createSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<InetAddress> getLocalIpAddresses() {
|
||||||
|
// If the device doesn't have wifi, don't open any sockets
|
||||||
|
if (wifiManager == null) return emptyList();
|
||||||
|
// If we're connected to a wifi network, use that network
|
||||||
|
WifiInfo info = wifiManager.getConnectionInfo();
|
||||||
|
if (info != null && info.getIpAddress() != 0)
|
||||||
|
return singletonList(intToInetAddress(info.getIpAddress()));
|
||||||
|
// If we're running an access point, return its address
|
||||||
|
if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
|
||||||
|
return singletonList(WIFI_AP_ADDRESS);
|
||||||
|
// No suitable addresses
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InetAddress intToInetAddress(int ip) {
|
||||||
|
byte[] ipBytes = new byte[4];
|
||||||
|
ipBytes[0] = (byte) (ip & 0xFF);
|
||||||
|
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
|
||||||
|
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
|
||||||
|
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress(ipBytes);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// Should only be thrown if address has illegal length
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On API 21 and later, a socket that is not created with the wifi
|
||||||
|
// network's socket factory may try to connect via another network
|
||||||
|
private SocketFactory getSocketFactory() {
|
||||||
|
if (SDK_INT < 21) return SocketFactory.getDefault();
|
||||||
|
for (Network net : connectivityManager.getAllNetworks()) {
|
||||||
|
NetworkInfo info = connectivityManager.getNetworkInfo(net);
|
||||||
|
if (info != null && info.getType() == TYPE_WIFI)
|
||||||
|
return net.getSocketFactory();
|
||||||
|
}
|
||||||
|
LOG.warning("Could not find suitable socket factory");
|
||||||
|
return SocketFactory.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
private class NetworkStateReceiver extends BroadcastReceiver {
|
private class NetworkStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context ctx, Intent i) {
|
public void onReceive(Context ctx, Intent i) {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (isApEnabledEvent(i)) {
|
||||||
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
// The state change may be broadcast before the AP address is
|
||||||
LOG.info("Connectivity change");
|
// visible, so delay handling the event
|
||||||
Bundle extras = i.getExtras();
|
scheduler.schedule(this::handleConnectivityChange, 1, SECONDS);
|
||||||
if (extras != null) {
|
|
||||||
LOG.info("Extras:");
|
|
||||||
for (String key : extras.keySet())
|
|
||||||
LOG.info("\t" + key + ": " + extras.get(key));
|
|
||||||
}
|
|
||||||
} else if (WIFI_AP_STATE_ACTION.equals(i.getAction())) {
|
|
||||||
int state = i.getIntExtra(EXTRA_WIFI_STATE, 0);
|
|
||||||
if (state == 13) LOG.info("Wifi AP enabled");
|
|
||||||
else LOG.info("Wifi AP state " + state);
|
|
||||||
}
|
|
||||||
logNetworkState(appContext, LOG);
|
|
||||||
}
|
|
||||||
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 {
|
} else {
|
||||||
LOG.info("Not connected to Wi-Fi");
|
handleConnectivityChange();
|
||||||
tryToClose(socket);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleConnectivityChange() {
|
||||||
|
if (!running) return;
|
||||||
|
Collection<InetAddress> addrs = getLocalIpAddresses();
|
||||||
|
if (addrs.contains(WIFI_AP_ADDRESS)) {
|
||||||
|
LOG.info("Providing wifi hotspot");
|
||||||
|
// There's no corresponding Network object and thus no way
|
||||||
|
// to get a suitable socket factory, so we won't be able to
|
||||||
|
// make outgoing connections on API 21+ if another network
|
||||||
|
// has internet access
|
||||||
|
socketFactory = SocketFactory.getDefault();
|
||||||
|
if (socket == null || socket.isClosed()) bind();
|
||||||
|
} else if (addrs.isEmpty()) {
|
||||||
|
LOG.info("Not connected to wifi");
|
||||||
|
socketFactory = SocketFactory.getDefault();
|
||||||
|
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,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@@ -27,12 +28,15 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
|||||||
private static final double BACKOFF_BASE = 1.2;
|
private static final double BACKOFF_BASE = 1.2;
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
|
|
||||||
public AndroidLanTcpPluginFactory(Executor ioExecutor,
|
public AndroidLanTcpPluginFactory(Executor ioExecutor,
|
||||||
BackoffFactory backoffFactory, Context appContext) {
|
ScheduledExecutorService scheduler, BackoffFactory backoffFactory,
|
||||||
|
Context appContext) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.scheduler = scheduler;
|
||||||
this.backoffFactory = backoffFactory;
|
this.backoffFactory = backoffFactory;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
}
|
}
|
||||||
@@ -51,7 +55,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
|||||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext,
|
return new AndroidLanTcpPlugin(ioExecutor, scheduler, backoff,
|
||||||
callback, MAX_LATENCY, MAX_IDLE_TIME);
|
appContext, callback, MAX_LATENCY, MAX_IDLE_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import android.os.PowerManager;
|
|||||||
import net.freehaven.tor.control.EventHandler;
|
import net.freehaven.tor.control.EventHandler;
|
||||||
import net.freehaven.tor.control.TorControlConnection;
|
import net.freehaven.tor.control.TorControlConnection;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.PoliteExecutor;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
@@ -63,8 +64,6 @@ import java.util.concurrent.Future;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
@@ -111,7 +110,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(TorPlugin.class.getName());
|
Logger.getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor, connectionStatusExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
@@ -125,7 +124,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||||
private final File doneFile, cookieFile;
|
private final File doneFile, cookieFile;
|
||||||
private final PowerManager.WakeLock wakeLock;
|
private final PowerManager.WakeLock wakeLock;
|
||||||
private final Lock connectionStatusLock;
|
|
||||||
private final AtomicReference<Future<?>> connectivityCheck =
|
private final AtomicReference<Future<?>> connectivityCheck =
|
||||||
new AtomicReference<>();
|
new AtomicReference<>();
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
@@ -167,7 +165,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
// This tag will prevent Huawei's powermanager from killing us.
|
// This tag will prevent Huawei's powermanager from killing us.
|
||||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||||
wakeLock.setReferenceCounted(false);
|
wakeLock.setReferenceCounted(false);
|
||||||
connectionStatusLock = new ReentrantLock();
|
// Don't execute more than one connection status check at a time
|
||||||
|
connectionStatusExecutor = new PoliteExecutor("TorPlugin",
|
||||||
|
ioExecutor, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -697,55 +697,46 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateConnectionStatus() {
|
private void updateConnectionStatus() {
|
||||||
ioExecutor.execute(() -> {
|
connectionStatusExecutor.execute(() -> {
|
||||||
if (!running) return;
|
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 {
|
try {
|
||||||
connectionStatusLock.lock();
|
if (!online) {
|
||||||
updateConnectionStatusLocked();
|
LOG.info("Disabling network, device is offline");
|
||||||
} finally {
|
enableNetwork(false);
|
||||||
connectionStatusLock.unlock();
|
} else if (blocked) {
|
||||||
|
LOG.info("Disabling network, country is blocked");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||||
|
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||||
|
LOG.info("Disabling network due to data setting");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else {
|
||||||
|
LOG.info("Enabling network");
|
||||||
|
enableNetwork(true);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: connectionStatusLock
|
|
||||||
private void updateConnectionStatusLocked() {
|
|
||||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
|
||||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
|
||||||
boolean online = net != null && net.isConnected();
|
|
||||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
|
||||||
String country = locationUtils.getCurrentCountry();
|
|
||||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(country);
|
|
||||||
Settings s = callback.getSettings();
|
|
||||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
|
||||||
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
|
||||||
if ("".equals(country)) LOG.info("Country code unknown");
|
|
||||||
else LOG.info("Country code: " + country);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!online) {
|
|
||||||
LOG.info("Disabling network, device is offline");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else if (blocked) {
|
|
||||||
LOG.info("Disabling network, country is blocked");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
|
||||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
|
||||||
LOG.info("Disabling network due to data setting");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else {
|
|
||||||
LOG.info("Enabling network");
|
|
||||||
enableNetwork(true);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleConnectionStatusUpdate() {
|
private void scheduleConnectionStatusUpdate() {
|
||||||
Future<?> newConnectivityCheck =
|
Future<?> newConnectivityCheck =
|
||||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||||
@@ -787,7 +778,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
private synchronized void enableNetwork(boolean enable) {
|
private synchronized void enableNetwork(boolean enable) {
|
||||||
networkEnabled = enable;
|
networkEnabled = enable;
|
||||||
circuitBuilt = false;
|
if (!enable) circuitBuilt = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean isConnected() {
|
private synchronized boolean isConnected() {
|
||||||
|
|||||||
@@ -1,41 +1,18 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.Network;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.net.wifi.WifiInfo;
|
|
||||||
import android.net.wifi.WifiManager;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InterfaceAddress;
|
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static android.content.Context.WIFI_SERVICE;
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
|
||||||
import static java.net.NetworkInterface.getNetworkInterfaces;
|
|
||||||
import static java.util.Collections.list;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.ipToString;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
|
||||||
|
|
||||||
@SuppressLint("HardwareIds")
|
|
||||||
public class AndroidUtils {
|
public class AndroidUtils {
|
||||||
|
|
||||||
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
|
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
|
||||||
@@ -46,7 +23,7 @@ public class AndroidUtils {
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public static Collection<String> getSupportedArchitectures() {
|
public static Collection<String> getSupportedArchitectures() {
|
||||||
List<String> abis = new ArrayList<>();
|
List<String> abis = new ArrayList<>();
|
||||||
if (SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS));
|
abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS));
|
||||||
} else {
|
} else {
|
||||||
abis.add(Build.CPU_ABI);
|
abis.add(Build.CPU_ABI);
|
||||||
@@ -90,123 +67,4 @@ public class AndroidUtils {
|
|||||||
public static File getReportDir(Context ctx) {
|
public static File getReportDir(Context ctx) {
|
||||||
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void logNetworkState(Context ctx, Logger logger) {
|
|
||||||
if (!logger.isLoggable(INFO)) return;
|
|
||||||
|
|
||||||
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
|
||||||
if (o == null) throw new AssertionError();
|
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
|
||||||
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
|
|
||||||
if (o == null) throw new AssertionError();
|
|
||||||
WifiManager wm = (WifiManager) o;
|
|
||||||
|
|
||||||
StringBuilder s = new StringBuilder();
|
|
||||||
logWifiInfo(s, wm.getConnectionInfo());
|
|
||||||
logNetworkInfo(s, cm.getActiveNetworkInfo(), true);
|
|
||||||
if (SDK_INT >= 21) {
|
|
||||||
for (Network network : cm.getAllNetworks())
|
|
||||||
logNetworkInfo(s, cm.getNetworkInfo(network), false);
|
|
||||||
} else {
|
|
||||||
for (NetworkInfo info : cm.getAllNetworkInfo())
|
|
||||||
logNetworkInfo(s, info, false);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (NetworkInterface iface : list(getNetworkInterfaces()))
|
|
||||||
logNetworkInterface(s, iface);
|
|
||||||
} catch (SocketException e) {
|
|
||||||
logger.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
logger.log(INFO, s.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logWifiInfo(StringBuilder s, @Nullable WifiInfo info) {
|
|
||||||
if (info == null) {
|
|
||||||
s.append("Wifi info: null\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
s.append("Wifi info:\n");
|
|
||||||
s.append("\tSSID: ").append(info.getSSID()).append("\n");
|
|
||||||
s.append("\tBSSID: ").append(info.getBSSID()).append("\n");
|
|
||||||
s.append("\tMAC address: ").append(info.getMacAddress()).append("\n");
|
|
||||||
s.append("\tIP address: ")
|
|
||||||
.append(ipToString(info.getIpAddress())).append("\n");
|
|
||||||
s.append("\tSupplicant state: ")
|
|
||||||
.append(info.getSupplicantState()).append("\n");
|
|
||||||
s.append("\tNetwork ID: ").append(info.getNetworkId()).append("\n");
|
|
||||||
s.append("\tLink speed: ").append(info.getLinkSpeed()).append("\n");
|
|
||||||
s.append("\tRSSI: ").append(info.getRssi()).append("\n");
|
|
||||||
if (info.getHiddenSSID()) s.append("\tHidden SSID\n");
|
|
||||||
if (SDK_INT >= 21)
|
|
||||||
s.append("\tFrequency: ").append(info.getFrequency()).append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logNetworkInfo(StringBuilder s,
|
|
||||||
@Nullable NetworkInfo info, boolean active) {
|
|
||||||
if (info == null) {
|
|
||||||
if (active) s.append("Active network info: null\n");
|
|
||||||
else s.append("Network info: null\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (active) s.append("Active network info:\n");
|
|
||||||
else s.append("Network info:\n");
|
|
||||||
s.append("\tType: ").append(info.getTypeName())
|
|
||||||
.append(" (").append(info.getType()).append(")\n");
|
|
||||||
s.append("\tSubtype: ").append(info.getSubtypeName())
|
|
||||||
.append(" (").append(info.getSubtype()).append(")\n");
|
|
||||||
s.append("\tState: ").append(info.getState()).append("\n");
|
|
||||||
s.append("\tDetailed state: ")
|
|
||||||
.append(info.getDetailedState()).append("\n");
|
|
||||||
s.append("\tReason: ").append(info.getReason()).append("\n");
|
|
||||||
s.append("\tExtra info: ").append(info.getExtraInfo()).append("\n");
|
|
||||||
if (info.isAvailable()) s.append("\tAvailable\n");
|
|
||||||
if (info.isConnected()) s.append("\tConnected\n");
|
|
||||||
if (info.isConnectedOrConnecting())
|
|
||||||
s.append("\tConnected or connecting\n");
|
|
||||||
if (info.isFailover()) s.append("\tFailover\n");
|
|
||||||
if (info.isRoaming()) s.append("\tRoaming\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logNetworkInterface(StringBuilder s,
|
|
||||||
NetworkInterface iface) throws SocketException {
|
|
||||||
s.append("Network interface:\n");
|
|
||||||
s.append("\tName: ").append(iface.getName()).append("\n");
|
|
||||||
s.append("\tDisplay name: ")
|
|
||||||
.append(iface.getDisplayName()).append("\n");
|
|
||||||
s.append("\tHardware address: ")
|
|
||||||
.append(hexOrNull(iface.getHardwareAddress())).append("\n");
|
|
||||||
if (iface.isLoopback()) s.append("\tLoopback\n");
|
|
||||||
if (iface.isPointToPoint()) s.append("\tPoint-to-point\n");
|
|
||||||
if (iface.isVirtual()) s.append("\tVirtual\n");
|
|
||||||
if (iface.isUp()) s.append("\tUp\n");
|
|
||||||
if (SDK_INT >= 19)
|
|
||||||
s.append("\tIndex: ").append(iface.getIndex()).append("\n");
|
|
||||||
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
|
|
||||||
s.append("\tInterface address:\n");
|
|
||||||
logInetAddress(s, addr.getAddress());
|
|
||||||
s.append("\t\tPrefix length: ")
|
|
||||||
.append(addr.getNetworkPrefixLength()).append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logInetAddress(StringBuilder s, InetAddress addr) {
|
|
||||||
s.append("\t\tAddress: ")
|
|
||||||
.append(hexOrNull(addr.getAddress())).append("\n");
|
|
||||||
s.append("\t\tHost address: ")
|
|
||||||
.append(addr.getHostAddress()).append("\n");
|
|
||||||
if (addr.isLoopbackAddress()) s.append("\t\tLoopback\n");
|
|
||||||
if (addr.isLinkLocalAddress()) s.append("\t\tLink-local\n");
|
|
||||||
if (addr.isSiteLocalAddress()) s.append("\t\tSite-local\n");
|
|
||||||
if (addr.isAnyLocalAddress()) s.append("\t\tAny local (wildcard)\n");
|
|
||||||
if (addr.isMCNodeLocal()) s.append("\t\tMulticast node-local\n");
|
|
||||||
if (addr.isMCLinkLocal()) s.append("\t\tMulticast link-local\n");
|
|
||||||
if (addr.isMCSiteLocal()) s.append("\t\tMulticast site-local\n");
|
|
||||||
if (addr.isMCOrgLocal()) s.append("\t\tMulticast org-local\n");
|
|
||||||
if (addr.isMCGlobal()) s.append("\t\tMulticast global\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String hexOrNull(@Nullable byte[] b) {
|
|
||||||
return b == null ? null : toHexString(b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
@@ -88,6 +90,10 @@ public interface ClientHelper {
|
|||||||
BdfDictionary toDictionary(byte[] b, int off, int len)
|
BdfDictionary toDictionary(byte[] b, int off, int len)
|
||||||
throws FormatException;
|
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, int off, int len) throws FormatException;
|
||||||
|
|
||||||
BdfList toList(byte[] b) throws FormatException;
|
BdfList toList(byte[] b) throws FormatException;
|
||||||
@@ -99,8 +105,15 @@ public interface ClientHelper {
|
|||||||
byte[] sign(String label, BdfList toSign, byte[] privateKey)
|
byte[] sign(String label, BdfList toSign, byte[] privateKey)
|
||||||
throws FormatException, GeneralSecurityException;
|
throws FormatException, GeneralSecurityException;
|
||||||
|
|
||||||
void verifySignature(String label, byte[] sig, byte[] publicKey,
|
void verifySignature(byte[] signature, String label, BdfList signed,
|
||||||
BdfList signed) throws FormatException, GeneralSecurityException;
|
byte[] publicKey) throws FormatException, GeneralSecurityException;
|
||||||
|
|
||||||
Author parseAndValidateAuthor(BdfList author) throws FormatException;
|
Author parseAndValidateAuthor(BdfList author) throws FormatException;
|
||||||
|
|
||||||
|
TransportProperties parseAndValidateTransportProperties(
|
||||||
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|
||||||
|
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
|
||||||
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ public interface ContactGroupFactory {
|
|||||||
/**
|
/**
|
||||||
* Creates a group that is not shared with any contacts.
|
* Creates a group that is not shared with any contacts.
|
||||||
*/
|
*/
|
||||||
Group createLocalGroup(ClientId clientId, int clientVersion);
|
Group createLocalGroup(ClientId clientId, int majorVersion);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a group for the given client to share with the given contact.
|
* Creates a group for the given client to share with the given contact.
|
||||||
*/
|
*/
|
||||||
Group createContactGroup(ClientId clientId, int clientVersion,
|
Group createContactGroup(ClientId clientId, int majorVersion,
|
||||||
Contact contact);
|
Contact contact);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a group for the given client to share between the given authors
|
* Creates a group for the given client to share between the given authors
|
||||||
* identified by their AuthorIds.
|
* identified by their AuthorIds.
|
||||||
*/
|
*/
|
||||||
Group createContactGroup(ClientId clientId, int clientVersion,
|
Group createContactGroup(ClientId clientId, int majorVersion,
|
||||||
AuthorId authorId1, AuthorId authorId2);
|
AuthorId authorId1, AuthorId authorId2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|||||||
public interface ContactExchangeTask {
|
public interface ContactExchangeTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version of the contact exchange protocol
|
* The current version of the contact exchange protocol.
|
||||||
*/
|
*/
|
||||||
int PROTOCOL_VERSION = 0;
|
byte PROTOCOL_VERSION = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for deriving Alice's header key from the master secret.
|
* Label for deriving Alice's header key from the master secret.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe wrapper for an integer that uniquely identifies a contact within
|
* Type-safe wrapper for an integer that uniquely identifies a contact within
|
||||||
* the scope of a single node.
|
* the scope of the local device.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -13,27 +14,37 @@ import java.util.Collection;
|
|||||||
public interface ContactManager {
|
public interface ContactManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a hook to be called whenever a contact is added.
|
* Registers a hook to be called whenever a contact is added or removed.
|
||||||
|
* This method should be called before
|
||||||
|
* {@link LifecycleManager#startServices(String)}.
|
||||||
*/
|
*/
|
||||||
void registerAddContactHook(AddContactHook hook);
|
void registerContactHook(ContactHook hook);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a hook to be called whenever a contact is removed.
|
* Stores a contact associated with the given local and remote pseudonyms,
|
||||||
*/
|
* derives and stores transport keys for each transport, and returns an ID
|
||||||
void registerRemoveContactHook(RemoveContactHook hook);
|
* for the contact.
|
||||||
|
*
|
||||||
/**
|
* @param alice true if the local party is Alice
|
||||||
* 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,
|
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;
|
boolean active) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a contact associated with the given local and remote pseudonyms,
|
* Stores a contact associated with the given local and remote pseudonyms
|
||||||
* and returns an ID for the contact.
|
* 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,
|
||||||
|
* derives and stores transport keys for each transport, and returns an ID
|
||||||
|
* for the contact.
|
||||||
|
*
|
||||||
|
* @param alice true if the local party is Alice
|
||||||
|
*/
|
||||||
ContactId addContact(Author remote, AuthorId local,
|
ContactId addContact(Author remote, AuthorId local,
|
||||||
SecretKey master, long timestamp, boolean alice, boolean verified,
|
SecretKey master, long timestamp, boolean alice, boolean verified,
|
||||||
boolean active) throws DbException;
|
boolean active) throws DbException;
|
||||||
@@ -94,11 +105,10 @@ public interface ContactManager {
|
|||||||
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
interface AddContactHook {
|
interface ContactHook {
|
||||||
void addingContact(Transaction txn, Contact c) throws DbException;
|
|
||||||
}
|
void addingContact(Transaction txn, Contact c) throws DbException;
|
||||||
|
|
||||||
interface RemoveContactHook {
|
|
||||||
void removingContact(Transaction txn, Contact c) throws DbException;
|
void removingContact(Transaction txn, Contact c) throws DbException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.briarproject.bramble.api.contact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record types for the contact exchange protocol.
|
||||||
|
*/
|
||||||
|
public interface RecordTypes {
|
||||||
|
|
||||||
|
byte CONTACT_INFO = 0;
|
||||||
|
}
|
||||||
@@ -67,8 +67,8 @@ public interface CryptoComponent {
|
|||||||
* signature created for another purpose
|
* signature created for another purpose
|
||||||
* @return true if the signature was valid, false otherwise.
|
* @return true if the signature was valid, false otherwise.
|
||||||
*/
|
*/
|
||||||
boolean verify(String label, byte[] signedData, byte[] publicKey,
|
boolean verifySignature(byte[] signature, String label, byte[] signed,
|
||||||
byte[] signature) throws GeneralSecurityException;
|
byte[] publicKey) throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hash of the given inputs. The inputs are unambiguously
|
* Returns the hash of the given inputs. The inputs are unambiguously
|
||||||
@@ -91,6 +91,18 @@ public interface CryptoComponent {
|
|||||||
*/
|
*/
|
||||||
byte[] mac(String label, SecretKey macKey, byte[]... inputs);
|
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
|
* Encrypts and authenticates the given plaintext so it can be written to
|
||||||
* storage. The encryption and authentication keys are derived from the
|
* storage. The encryption and authentication keys are derived from the
|
||||||
|
|||||||
@@ -16,4 +16,10 @@ public interface CryptoConstants {
|
|||||||
* The maximum length of a signature in bytes.
|
* The maximum length of a signature in bytes.
|
||||||
*/
|
*/
|
||||||
int MAX_SIGNATURE_BYTES = 64;
|
int MAX_SIGNATURE_BYTES = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of a MAC in bytes.
|
||||||
|
*/
|
||||||
|
int MAC_BYTES = SecretKey.LENGTH;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ public interface TransportCrypto {
|
|||||||
* rotation period from the given master secret.
|
* rotation period from the given master secret.
|
||||||
*
|
*
|
||||||
* @param alice whether the keys are for use by Alice or Bob.
|
* @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,
|
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
||||||
long rotationPeriod, boolean alice);
|
long rotationPeriod, boolean alice, boolean active);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotates the given transport keys to the given rotation period. If the
|
* Rotates the given transport keys to the given rotation period. If the
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ public class BdfDictionary extends TreeMap<String, Object> {
|
|||||||
* );
|
* );
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public static BdfDictionary of(Entry<String, Object>... entries) {
|
public static BdfDictionary of(Entry<String, ?>... entries) {
|
||||||
BdfDictionary d = new BdfDictionary();
|
BdfDictionary d = new BdfDictionary();
|
||||||
for (Entry<String, Object> e : entries) d.put(e.getKey(), e.getValue());
|
for (Entry<String, ?> e : entries) d.put(e.getKey(), e.getValue());
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ public class BdfDictionary extends TreeMap<String, Object> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BdfDictionary(Map<String, Object> m) {
|
public BdfDictionary(Map<String, ?> m) {
|
||||||
super(m);
|
super(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ import org.briarproject.bramble.api.sync.MessageId;
|
|||||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
import org.briarproject.bramble.api.transport.KeySet;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -103,10 +104,17 @@ public interface DatabaseComponent {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores transport keys for a newly added contact.
|
* Stores the given transport keys, optionally binding them to the given
|
||||||
|
* contact, and returns a key set ID.
|
||||||
*/
|
*/
|
||||||
void addTransportKeys(Transaction txn, ContactId c, TransportKeys k)
|
KeySetId addTransportKeys(Transaction txn, @Nullable ContactId c,
|
||||||
throws DbException;
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the database contains the given contact for the given
|
* Returns true if the database contains the given contact for the given
|
||||||
@@ -233,7 +241,8 @@ public interface DatabaseComponent {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException;
|
Collection<Group> getGroups(Transaction txn, ClientId c, int majorVersion)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given group's visibility to the given contact, or
|
* Returns the given group's visibility to the given contact, or
|
||||||
@@ -258,6 +267,14 @@ public interface DatabaseComponent {
|
|||||||
*/
|
*/
|
||||||
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
|
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the IDs of all messages in the given group.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that need to be validated.
|
* Returns the IDs of any messages that need to be validated.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -339,12 +356,8 @@ public interface DatabaseComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs and states of all dependencies of the given message.
|
* Returns the IDs and states of all dependencies of the given message.
|
||||||
* Missing dependencies have the state
|
* For missing dependencies and dependencies in other groups, the state
|
||||||
* {@link ValidationManager.State UNKNOWN}.
|
* {@link State UNKNOWN} is returned.
|
||||||
* 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/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
@@ -352,9 +365,9 @@ public interface DatabaseComponent {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all IDs of messages that depend on the given message.
|
* Returns the IDs and states of all dependents of the given message.
|
||||||
* Messages in other groups that declare a dependency on the given message
|
* Dependents in other groups are not returned. If the given message is
|
||||||
* will be returned even though such dependencies are invalid.
|
* missing, no dependents are returned.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
@@ -399,15 +412,14 @@ public interface DatabaseComponent {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Map<ContactId, TransportKeys> getTransportKeys(Transaction txn,
|
Collection<KeySet> getTransportKeys(Transaction txn, TransportId t)
|
||||||
TransportId t) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given contact and
|
* Increments the outgoing stream counter for the given transport keys.
|
||||||
* transport in the given rotation period .
|
|
||||||
*/
|
*/
|
||||||
void incrementStreamCounter(Transaction txn, ContactId c, TransportId t,
|
void incrementStreamCounter(Transaction txn, TransportId t, KeySetId k)
|
||||||
long rotationPeriod) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges the given metadata with the existing metadata for the given
|
* Merges the given metadata with the existing metadata for the given
|
||||||
@@ -477,6 +489,12 @@ public interface DatabaseComponent {
|
|||||||
*/
|
*/
|
||||||
void removeTransport(Transaction txn, TransportId t) throws DbException;
|
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.
|
* Marks the given contact as verified.
|
||||||
*/
|
*/
|
||||||
@@ -512,15 +530,21 @@ public interface DatabaseComponent {
|
|||||||
Collection<MessageId> dependencies) throws DbException;
|
Collection<MessageId> dependencies) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the reordering window for the given contact and transport in the
|
* Sets the reordering window for the given key set and transport in the
|
||||||
* given rotation period.
|
* given rotation period.
|
||||||
*/
|
*/
|
||||||
void setReorderingWindow(Transaction txn, ContactId c, TransportId t,
|
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
|
||||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
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.
|
* Stores the given transport keys, deleting any keys they have replaced.
|
||||||
*/
|
*/
|
||||||
void updateTransportKeys(Transaction txn,
|
void updateTransportKeys(Transaction txn, Collection<KeySet> keys)
|
||||||
Map<ContactId, TransportKeys> keys) throws DbException;
|
throws DbException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.briarproject.bramble.api.keyagreement.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when a BQP task stops listening.
|
||||||
|
*/
|
||||||
|
public class KeyAgreementStoppedListeningEvent extends Event {
|
||||||
|
}
|
||||||
@@ -43,17 +43,20 @@ public interface LifecycleManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a {@link Service} to be started and stopped.
|
* Registers a {@link Service} to be started and stopped. This method
|
||||||
|
* should be called before {@link #startServices(String)}.
|
||||||
*/
|
*/
|
||||||
void registerService(Service s);
|
void registerService(Service s);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a {@link Client} to be started.
|
* Registers a {@link Client} to be started. This method should be called
|
||||||
|
* before {@link #startServices(String)}.
|
||||||
*/
|
*/
|
||||||
void registerClient(Client c);
|
void registerClient(Client c);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers an {@link ExecutorService} to be shut down.
|
* Registers an {@link ExecutorService} to be shut down. This method
|
||||||
|
* should be called before {@link #startServices(String)}.
|
||||||
*/
|
*/
|
||||||
void registerForShutdown(ExecutorService e);
|
void registerForShutdown(ExecutorService e);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
package org.briarproject.bramble.api.plugin;
|
package org.briarproject.bramble.api.plugin;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe wrapper for a string that uniquely identifies a transport plugin.
|
* Type-safe wrapper for a namespaced string that uniquely identifies a
|
||||||
|
* transport plugin.
|
||||||
*/
|
*/
|
||||||
public class TransportId {
|
public class TransportId {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum length of transport identifier in UTF-8 bytes.
|
* The maximum length of a transport identifier in UTF-8 bytes.
|
||||||
*/
|
*/
|
||||||
public static int MAX_TRANSPORT_ID_LENGTH = 64;
|
public static int MAX_TRANSPORT_ID_LENGTH = 100;
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
|
|
||||||
public TransportId(String id) {
|
public TransportId(String id) {
|
||||||
byte[] b = id.getBytes(Charset.forName("UTF-8"));
|
int length = StringUtils.toUtf8(id).length;
|
||||||
if (b.length == 0 || b.length > MAX_TRANSPORT_ID_LENGTH)
|
if (length == 0 || length > MAX_TRANSPORT_ID_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,17 @@ public interface TransportPropertyManager {
|
|||||||
/**
|
/**
|
||||||
* The unique ID of the transport property client.
|
* The unique ID of the transport property client.
|
||||||
*/
|
*/
|
||||||
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties");
|
ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.properties");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version of the transport property client.
|
* The current major version of the transport property client.
|
||||||
*/
|
*/
|
||||||
int CLIENT_VERSION = 0;
|
int MAJOR_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current minor version of the transport property client.
|
||||||
|
*/
|
||||||
|
int MINOR_VERSION = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given properties received while adding a contact - they will
|
* Stores the given properties received while adding a contact - they will
|
||||||
@@ -37,8 +42,8 @@ public interface TransportPropertyManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the local transport properties for all transports.
|
* Returns the local transport properties for all transports.
|
||||||
* <br/>
|
* <p/>
|
||||||
* TODO: Transaction can be read-only when code is simplified
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
|
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.briarproject.bramble.api.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class Record {
|
||||||
|
|
||||||
|
public static final int RECORD_HEADER_BYTES = 4;
|
||||||
|
public static final int MAX_RECORD_PAYLOAD_BYTES = 48 * 1024; // 48 KiB
|
||||||
|
|
||||||
|
private final byte protocolVersion, recordType;
|
||||||
|
private final byte[] payload;
|
||||||
|
|
||||||
|
public Record(byte protocolVersion, byte recordType, byte[] payload) {
|
||||||
|
if (payload.length > MAX_RECORD_PAYLOAD_BYTES)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
this.protocolVersion = protocolVersion;
|
||||||
|
this.recordType = recordType;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getProtocolVersion() {
|
||||||
|
return protocolVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getRecordType() {
|
||||||
|
return recordType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPayload() {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.briarproject.bramble.api.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface RecordReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the next record.
|
||||||
|
*
|
||||||
|
* @throws EOFException if the end of the stream is reached without reading
|
||||||
|
* a complete record
|
||||||
|
*/
|
||||||
|
Record readRecord() throws IOException;
|
||||||
|
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.briarproject.bramble.api.record;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public interface RecordReaderFactory {
|
||||||
|
|
||||||
|
RecordReader createRecordReader(InputStream in);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.briarproject.bramble.api.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface RecordWriter {
|
||||||
|
|
||||||
|
void writeRecord(Record r) throws IOException;
|
||||||
|
|
||||||
|
void flush() throws IOException;
|
||||||
|
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.briarproject.bramble.api.record;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public interface RecordWriterFactory {
|
||||||
|
|
||||||
|
RecordWriter createRecordWriter(OutputStream out);
|
||||||
|
}
|
||||||
@@ -1,19 +1,29 @@
|
|||||||
package org.briarproject.bramble.api.sync;
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper for a name-spaced string that uniquely identifies a sync client.
|
* Type-safe wrapper for a namespaced string that uniquely identifies a sync
|
||||||
|
* client.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ClientId implements Comparable<ClientId> {
|
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;
|
private final String id;
|
||||||
|
|
||||||
public ClientId(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;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,43 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPT
|
|||||||
public class Group {
|
public class Group {
|
||||||
|
|
||||||
public enum Visibility {
|
public enum Visibility {
|
||||||
INVISIBLE, // The group is not visible
|
|
||||||
VISIBLE, // The group is visible but messages are not shared
|
INVISIBLE(0), // The group is not visible
|
||||||
SHARED // The group is visible and messages are shared
|
VISIBLE(1), // The group is visible, messages are accepted but not sent
|
||||||
|
SHARED(2); // The group is visible, messages are accepted and sent
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
Visibility(int value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Visibility min(Visibility a, Visibility b) {
|
||||||
|
return a.getValue() < b.getValue() ? a : b;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version of the group format.
|
||||||
|
*/
|
||||||
|
public static final int FORMAT_VERSION = 1;
|
||||||
|
|
||||||
private final GroupId id;
|
private final GroupId id;
|
||||||
private final ClientId clientId;
|
private final ClientId clientId;
|
||||||
|
private final int majorVersion;
|
||||||
private final byte[] descriptor;
|
private final byte[] descriptor;
|
||||||
|
|
||||||
public Group(GroupId id, ClientId clientId, byte[] descriptor) {
|
public Group(GroupId id, ClientId clientId, int majorVersion,
|
||||||
|
byte[] descriptor) {
|
||||||
if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
|
if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.clientId = clientId;
|
this.clientId = clientId;
|
||||||
|
this.majorVersion = majorVersion;
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +59,13 @@ public class Group {
|
|||||||
return clientId;
|
return clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the major version of the client to which the group belongs.
|
||||||
|
*/
|
||||||
|
public int getMajorVersion() {
|
||||||
|
return majorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the group's descriptor.
|
* Returns the group's descriptor.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
public interface GroupFactory {
|
public interface GroupFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a group with the given client ID, client version and descriptor.
|
* Creates a group with the given client ID, major version and descriptor.
|
||||||
*/
|
*/
|
||||||
Group createGroup(ClientId c, int clientVersion, byte[] descriptor);
|
Group createGroup(ClientId c, int majorVersion, byte[] descriptor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
|
|||||||
|
|
||||||
public class Message {
|
public class Message {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current version of the message format.
|
||||||
|
*/
|
||||||
|
public static final int FORMAT_VERSION = 1;
|
||||||
|
|
||||||
private final MessageId id;
|
private final MessageId id;
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ public interface MessageFactory {
|
|||||||
|
|
||||||
Message createMessage(GroupId g, long timestamp, byte[] body);
|
Message createMessage(GroupId g, long timestamp, byte[] body);
|
||||||
|
|
||||||
|
Message createMessage(byte[] raw);
|
||||||
|
|
||||||
Message createMessage(MessageId m, byte[] raw);
|
Message createMessage(MessageId m, byte[] raw);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,13 @@ public class MessageId extends UniqueId {
|
|||||||
/**
|
/**
|
||||||
* Label for hashing messages to calculate their identifiers.
|
* Label for hashing messages to calculate their identifiers.
|
||||||
*/
|
*/
|
||||||
public static final String LABEL = "org.briarproject.bramble/MESSAGE_ID";
|
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 MessageId(byte[] id) {
|
public MessageId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.briarproject.bramble.api.sync;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
|
|
||||||
public interface SyncConstants {
|
public interface SyncConstants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,16 +12,8 @@ public interface SyncConstants {
|
|||||||
byte PROTOCOL_VERSION = 0;
|
byte PROTOCOL_VERSION = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the record header in bytes.
|
* The maximum length of a group descriptor in bytes.
|
||||||
*/
|
*/
|
||||||
int RECORD_HEADER_LENGTH = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum length of the record payload in bytes.
|
|
||||||
*/
|
|
||||||
int MAX_RECORD_PAYLOAD_LENGTH = 48 * 1024; // 48 KiB
|
|
||||||
|
|
||||||
/** The maximum length of a group descriptor in bytes. */
|
|
||||||
int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB
|
int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,5 +34,5 @@ public interface SyncConstants {
|
|||||||
/**
|
/**
|
||||||
* The maximum number of message IDs in an ack, offer or request record.
|
* The maximum number of message IDs in an ack, offer or request record.
|
||||||
*/
|
*/
|
||||||
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_LENGTH / UniqueId.LENGTH;
|
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface RecordReader {
|
public interface SyncRecordReader {
|
||||||
|
|
||||||
boolean eof() throws IOException;
|
boolean eof() throws IOException;
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface RecordReaderFactory {
|
public interface SyncRecordReaderFactory {
|
||||||
|
|
||||||
RecordReader createRecordReader(InputStream in);
|
SyncRecordReader createRecordReader(InputStream in);
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface RecordWriter {
|
public interface SyncRecordWriter {
|
||||||
|
|
||||||
void writeAck(Ack a) throws IOException;
|
void writeAck(Ack a) throws IOException;
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface RecordWriterFactory {
|
public interface SyncRecordWriterFactory {
|
||||||
|
|
||||||
RecordWriter createRecordWriter(OutputStream out);
|
SyncRecordWriter createRecordWriter(OutputStream out);
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.sync;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,15 +34,20 @@ public interface ValidationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the message validator for the given client.
|
* Registers the message validator for the given client. This method
|
||||||
|
* should be called before {@link LifecycleManager#startServices(String)}.
|
||||||
*/
|
*/
|
||||||
void registerMessageValidator(ClientId c, MessageValidator v);
|
void registerMessageValidator(ClientId c, int majorVersion,
|
||||||
|
MessageValidator v);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the incoming message hook for the given client. The hook will be
|
* Registers the incoming message hook for the given client. The hook will
|
||||||
* called once for each incoming message that passes validation.
|
* be called once for each incoming message that passes validation. This
|
||||||
|
* method should be called before
|
||||||
|
* {@link LifecycleManager#startServices(String)}.
|
||||||
*/
|
*/
|
||||||
void registerIncomingMessageHook(ClientId c, IncomingMessageHook hook);
|
void registerIncomingMessageHook(ClientId c, int majorVersion,
|
||||||
|
IncomingMessageHook hook);
|
||||||
|
|
||||||
interface MessageValidator {
|
interface MessageValidator {
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,13 +18,55 @@ public interface KeyManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the key manager that a new contact has been added. Derives and
|
* Informs the key manager that a new contact has been added. Derives and
|
||||||
* stores transport keys for communicating with the contact.
|
* stores a set of transport keys for communicating with the contact over
|
||||||
|
* each transport.
|
||||||
|
* <p/>
|
||||||
* {@link StreamContext StreamContexts} for the contact can be created
|
* {@link StreamContext StreamContexts} for the contact can be created
|
||||||
* after this method has returned.
|
* after this method has returned.
|
||||||
|
*
|
||||||
|
* @param alice true if the local party is Alice
|
||||||
*/
|
*/
|
||||||
void addContact(Transaction txn, ContactId c, SecretKey master,
|
void addContact(Transaction txn, ContactId c, SecretKey master,
|
||||||
long timestamp, boolean alice) throws DbException;
|
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.
|
||||||
|
*
|
||||||
|
* @param alice true if the local party is Alice
|
||||||
|
*/
|
||||||
|
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
|
* Returns a {@link StreamContext} for sending a stream to the given
|
||||||
* contact over the given transport, or null if an error occurs or the
|
* contact over the given transport, or null if an error occurs or the
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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,18 +10,20 @@ public class OutgoingKeys {
|
|||||||
|
|
||||||
private final SecretKey tagKey, headerKey;
|
private final SecretKey tagKey, headerKey;
|
||||||
private final long rotationPeriod, streamCounter;
|
private final long rotationPeriod, streamCounter;
|
||||||
|
private final boolean active;
|
||||||
|
|
||||||
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||||
long rotationPeriod) {
|
long rotationPeriod, boolean active) {
|
||||||
this(tagKey, headerKey, rotationPeriod, 0);
|
this(tagKey, headerKey, rotationPeriod, 0, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
public OutgoingKeys(SecretKey tagKey, SecretKey headerKey,
|
||||||
long rotationPeriod, long streamCounter) {
|
long rotationPeriod, long streamCounter, boolean active) {
|
||||||
this.tagKey = tagKey;
|
this.tagKey = tagKey;
|
||||||
this.headerKey = headerKey;
|
this.headerKey = headerKey;
|
||||||
this.rotationPeriod = rotationPeriod;
|
this.rotationPeriod = rotationPeriod;
|
||||||
this.streamCounter = streamCounter;
|
this.streamCounter = streamCounter;
|
||||||
|
this.active = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey getTagKey() {
|
public SecretKey getTagKey() {
|
||||||
@@ -39,4 +41,8 @@ public class OutgoingKeys {
|
|||||||
public long getStreamCounter() {
|
public long getStreamCounter() {
|
||||||
return streamCounter;
|
return streamCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.briarproject.bramble.api.versioning;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class ClientMajorVersion implements Comparable<ClientMajorVersion> {
|
||||||
|
|
||||||
|
private final ClientId clientId;
|
||||||
|
private final int majorVersion;
|
||||||
|
|
||||||
|
public ClientMajorVersion(ClientId clientId, int majorVersion) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.majorVersion = majorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientId getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMajorVersion() {
|
||||||
|
return majorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof ClientMajorVersion) {
|
||||||
|
ClientMajorVersion cv = (ClientMajorVersion) o;
|
||||||
|
return clientId.equals(cv.clientId)
|
||||||
|
&& majorVersion == cv.majorVersion;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return (clientId.hashCode() << 16) + majorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ClientMajorVersion cv) {
|
||||||
|
int compare = clientId.compareTo(cv.clientId);
|
||||||
|
if (compare != 0) return compare;
|
||||||
|
return majorVersion - cv.majorVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.briarproject.bramble.api.versioning;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface ClientVersioningManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique ID of the versioning client.
|
||||||
|
*/
|
||||||
|
ClientId CLIENT_ID = new ClientId("org.briarproject.bramble.versioning");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current major version of the versioning client.
|
||||||
|
*/
|
||||||
|
int MAJOR_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a client that will be advertised to contacts. The hook will
|
||||||
|
* be called when the visibility of the client changes. This method should
|
||||||
|
* be called before {@link LifecycleManager#startServices(String)}.
|
||||||
|
*/
|
||||||
|
void registerClient(ClientId clientId, int majorVersion, int minorVersion,
|
||||||
|
ClientVersioningHook hook);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the visibility of the given client with respect to the given
|
||||||
|
* contact.
|
||||||
|
*/
|
||||||
|
Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||||
|
ClientId clientId, int majorVersion) throws DbException;
|
||||||
|
|
||||||
|
interface ClientVersioningHook {
|
||||||
|
|
||||||
|
void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||||
|
Visibility v) throws DbException;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,14 +146,6 @@ public class StringUtils {
|
|||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String ipToString(int ip) {
|
|
||||||
int ip1 = ip & 0xFF;
|
|
||||||
int ip2 = (ip >> 8) & 0xFF;
|
|
||||||
int ip3 = (ip >> 16) & 0xFF;
|
|
||||||
int ip4 = (ip >> 24) & 0xFF;
|
|
||||||
return ip1 + "." + ip2 + "." + ip3 + "." + ip4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRandomString(int length) {
|
public static String getRandomString(int length) {
|
||||||
char[] c = new char[length];
|
char[] c = new char[length];
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
|||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
|
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.ClientId;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
@@ -16,13 +18,18 @@ import java.io.File;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
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_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_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_GROUP_DESCRIPTOR_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
@@ -54,6 +61,33 @@ public class TestUtils {
|
|||||||
return getRandomBytes(UniqueId.LENGTH);
|
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() {
|
public static SecretKey getSecretKey() {
|
||||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||||
}
|
}
|
||||||
@@ -83,15 +117,16 @@ public class TestUtils {
|
|||||||
return new Author(id, FORMAT_VERSION, name, publicKey);
|
return new Author(id, FORMAT_VERSION, name, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Group getGroup(ClientId clientId) {
|
public static Group getGroup(ClientId clientId, int majorVersion) {
|
||||||
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||||
return getGroup(clientId, descriptorLength);
|
return getGroup(clientId, majorVersion, descriptorLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
public static Group getGroup(ClientId clientId, int majorVersion,
|
||||||
|
int descriptorLength) {
|
||||||
GroupId groupId = new GroupId(getRandomId());
|
GroupId groupId = new GroupId(getRandomId());
|
||||||
byte[] descriptor = getRandomBytes(descriptorLength);
|
byte[] descriptor = getRandomBytes(descriptorLength);
|
||||||
return new Group(groupId, clientId, descriptor);
|
return new Group(groupId, clientId, majorVersion, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Message getMessage(GroupId groupId) {
|
public static Message getMessage(GroupId groupId) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.briarproject.bramble;
|
package org.briarproject.bramble;
|
||||||
|
|
||||||
import org.briarproject.bramble.contact.ContactModule;
|
import org.briarproject.bramble.contact.ContactModule;
|
||||||
import org.briarproject.bramble.crypto.CryptoModule;
|
import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||||
import org.briarproject.bramble.identity.IdentityModule;
|
import org.briarproject.bramble.identity.IdentityModule;
|
||||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
@@ -10,12 +10,13 @@ import org.briarproject.bramble.properties.PropertiesModule;
|
|||||||
import org.briarproject.bramble.sync.SyncModule;
|
import org.briarproject.bramble.sync.SyncModule;
|
||||||
import org.briarproject.bramble.system.SystemModule;
|
import org.briarproject.bramble.system.SystemModule;
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
public interface BrambleCoreEagerSingletons {
|
public interface BrambleCoreEagerSingletons {
|
||||||
|
|
||||||
void inject(ContactModule.EagerSingletons init);
|
void inject(ContactModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(CryptoModule.EagerSingletons init);
|
void inject(CryptoExecutorModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(DatabaseExecutorModule.EagerSingletons init);
|
void inject(DatabaseExecutorModule.EagerSingletons init);
|
||||||
|
|
||||||
@@ -32,4 +33,6 @@ public interface BrambleCoreEagerSingletons {
|
|||||||
void inject(SystemModule.EagerSingletons init);
|
void inject(SystemModule.EagerSingletons init);
|
||||||
|
|
||||||
void inject(TransportModule.EagerSingletons init);
|
void inject(TransportModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(VersioningModule.EagerSingletons init);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble;
|
|||||||
|
|
||||||
import org.briarproject.bramble.client.ClientModule;
|
import org.briarproject.bramble.client.ClientModule;
|
||||||
import org.briarproject.bramble.contact.ContactModule;
|
import org.briarproject.bramble.contact.ContactModule;
|
||||||
|
import org.briarproject.bramble.crypto.CryptoExecutorModule;
|
||||||
import org.briarproject.bramble.crypto.CryptoModule;
|
import org.briarproject.bramble.crypto.CryptoModule;
|
||||||
import org.briarproject.bramble.data.DataModule;
|
import org.briarproject.bramble.data.DataModule;
|
||||||
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
import org.briarproject.bramble.db.DatabaseExecutorModule;
|
||||||
@@ -12,6 +13,7 @@ import org.briarproject.bramble.keyagreement.KeyAgreementModule;
|
|||||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
import org.briarproject.bramble.plugin.PluginModule;
|
import org.briarproject.bramble.plugin.PluginModule;
|
||||||
import org.briarproject.bramble.properties.PropertiesModule;
|
import org.briarproject.bramble.properties.PropertiesModule;
|
||||||
|
import org.briarproject.bramble.record.RecordModule;
|
||||||
import org.briarproject.bramble.reliability.ReliabilityModule;
|
import org.briarproject.bramble.reliability.ReliabilityModule;
|
||||||
import org.briarproject.bramble.reporting.ReportingModule;
|
import org.briarproject.bramble.reporting.ReportingModule;
|
||||||
import org.briarproject.bramble.settings.SettingsModule;
|
import org.briarproject.bramble.settings.SettingsModule;
|
||||||
@@ -19,6 +21,7 @@ import org.briarproject.bramble.socks.SocksModule;
|
|||||||
import org.briarproject.bramble.sync.SyncModule;
|
import org.briarproject.bramble.sync.SyncModule;
|
||||||
import org.briarproject.bramble.system.SystemModule;
|
import org.briarproject.bramble.system.SystemModule;
|
||||||
import org.briarproject.bramble.transport.TransportModule;
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
|
import org.briarproject.bramble.versioning.VersioningModule;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ import dagger.Module;
|
|||||||
ClientModule.class,
|
ClientModule.class,
|
||||||
ContactModule.class,
|
ContactModule.class,
|
||||||
CryptoModule.class,
|
CryptoModule.class,
|
||||||
|
CryptoExecutorModule.class,
|
||||||
DataModule.class,
|
DataModule.class,
|
||||||
DatabaseModule.class,
|
DatabaseModule.class,
|
||||||
DatabaseExecutorModule.class,
|
DatabaseExecutorModule.class,
|
||||||
@@ -35,19 +39,21 @@ import dagger.Module;
|
|||||||
LifecycleModule.class,
|
LifecycleModule.class,
|
||||||
PluginModule.class,
|
PluginModule.class,
|
||||||
PropertiesModule.class,
|
PropertiesModule.class,
|
||||||
|
RecordModule.class,
|
||||||
ReliabilityModule.class,
|
ReliabilityModule.class,
|
||||||
ReportingModule.class,
|
ReportingModule.class,
|
||||||
SettingsModule.class,
|
SettingsModule.class,
|
||||||
SocksModule.class,
|
SocksModule.class,
|
||||||
SyncModule.class,
|
SyncModule.class,
|
||||||
SystemModule.class,
|
SystemModule.class,
|
||||||
TransportModule.class
|
TransportModule.class,
|
||||||
|
VersioningModule.class
|
||||||
})
|
})
|
||||||
public class BrambleCoreModule {
|
public class BrambleCoreModule {
|
||||||
|
|
||||||
public static void initEagerSingletons(BrambleCoreEagerSingletons c) {
|
public static void initEagerSingletons(BrambleCoreEagerSingletons c) {
|
||||||
c.inject(new ContactModule.EagerSingletons());
|
c.inject(new ContactModule.EagerSingletons());
|
||||||
c.inject(new CryptoModule.EagerSingletons());
|
c.inject(new CryptoExecutorModule.EagerSingletons());
|
||||||
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
c.inject(new DatabaseExecutorModule.EagerSingletons());
|
||||||
c.inject(new IdentityModule.EagerSingletons());
|
c.inject(new IdentityModule.EagerSingletons());
|
||||||
c.inject(new LifecycleModule.EagerSingletons());
|
c.inject(new LifecycleModule.EagerSingletons());
|
||||||
@@ -56,5 +62,6 @@ public class BrambleCoreModule {
|
|||||||
c.inject(new SyncModule.EagerSingletons());
|
c.inject(new SyncModule.EagerSingletons());
|
||||||
c.inject(new SystemModule.EagerSingletons());
|
c.inject(new SystemModule.EagerSingletons());
|
||||||
c.inject(new TransportModule.EagerSingletons());
|
c.inject(new TransportModule.EagerSingletons());
|
||||||
|
c.inject(new VersioningModule.EagerSingletons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import org.briarproject.bramble.api.db.Transaction;
|
|||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
@@ -37,6 +39,8 @@ import javax.inject.Inject;
|
|||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
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_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_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.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
@@ -324,6 +328,20 @@ 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
|
@Override
|
||||||
public BdfList toList(byte[] b, int off, int len) throws FormatException {
|
public BdfList toList(byte[] b, int off, int len) throws FormatException {
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
|
ByteArrayInputStream in = new ByteArrayInputStream(b, off, len);
|
||||||
@@ -363,9 +381,10 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verifySignature(String label, byte[] sig, byte[] publicKey,
|
public void verifySignature(byte[] signature, String label, BdfList signed,
|
||||||
BdfList signed) throws FormatException, GeneralSecurityException {
|
byte[] publicKey) throws FormatException, GeneralSecurityException {
|
||||||
if (!crypto.verify(label, toByteArray(signed), publicKey, sig)) {
|
if (!crypto.verifySignature(signature, label, toByteArray(signed),
|
||||||
|
publicKey)) {
|
||||||
throw new GeneralSecurityException("Invalid signature");
|
throw new GeneralSecurityException("Invalid signature");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,4 +401,33 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
||||||
return authorFactory.createAuthor(formatVersion, name, publicKey);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,25 +32,25 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createLocalGroup(ClientId clientId, int clientVersion) {
|
public Group createLocalGroup(ClientId clientId, int majorVersion) {
|
||||||
return groupFactory.createGroup(clientId, clientVersion,
|
return groupFactory.createGroup(clientId, majorVersion,
|
||||||
LOCAL_GROUP_DESCRIPTOR);
|
LOCAL_GROUP_DESCRIPTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
public Group createContactGroup(ClientId clientId, int majorVersion,
|
||||||
Contact contact) {
|
Contact contact) {
|
||||||
AuthorId local = contact.getLocalAuthorId();
|
AuthorId local = contact.getLocalAuthorId();
|
||||||
AuthorId remote = contact.getAuthor().getId();
|
AuthorId remote = contact.getAuthor().getId();
|
||||||
byte[] descriptor = createGroupDescriptor(local, remote);
|
byte[] descriptor = createGroupDescriptor(local, remote);
|
||||||
return groupFactory.createGroup(clientId, clientVersion, descriptor);
|
return groupFactory.createGroup(clientId, majorVersion, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
public Group createContactGroup(ClientId clientId, int majorVersion,
|
||||||
AuthorId authorId1, AuthorId authorId2) {
|
AuthorId authorId1, AuthorId authorId2) {
|
||||||
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
|
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
|
||||||
return groupFactory.createGroup(clientId, clientVersion, descriptor);
|
return groupFactory.createGroup(clientId, majorVersion, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
package org.briarproject.bramble.contact;
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeListener;
|
import org.briarproject.bramble.api.contact.ContactExchangeListener;
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriter;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
@@ -26,30 +23,30 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||||
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO;
|
||||||
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.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -62,9 +59,9 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
"org.briarproject.briar.contact/EXCHANGE";
|
"org.briarproject.briar.contact/EXCHANGE";
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final AuthorFactory authorFactory;
|
private final ClientHelper clientHelper;
|
||||||
private final BdfReaderFactory bdfReaderFactory;
|
private final RecordReaderFactory recordReaderFactory;
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
private final RecordWriterFactory recordWriterFactory;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final ConnectionManager connectionManager;
|
private final ConnectionManager connectionManager;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
@@ -81,17 +78,17 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
private volatile boolean alice;
|
private volatile boolean alice;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContactExchangeTaskImpl(DatabaseComponent db,
|
ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||||
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
RecordReaderFactory recordReaderFactory,
|
||||||
BdfWriterFactory bdfWriterFactory, Clock clock,
|
RecordWriterFactory recordWriterFactory, Clock clock,
|
||||||
ConnectionManager connectionManager, ContactManager contactManager,
|
ConnectionManager connectionManager, ContactManager contactManager,
|
||||||
TransportPropertyManager transportPropertyManager,
|
TransportPropertyManager transportPropertyManager,
|
||||||
CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
|
CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
|
||||||
StreamWriterFactory streamWriterFactory) {
|
StreamWriterFactory streamWriterFactory) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.authorFactory = authorFactory;
|
this.clientHelper = clientHelper;
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
this.recordReaderFactory = recordReaderFactory;
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
this.recordWriterFactory = recordWriterFactory;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
@@ -126,18 +123,18 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
listener.contactExchangeFailed();
|
listener.contactExchangeFailed();
|
||||||
tryToClose(conn, true);
|
tryToClose(conn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the local transport properties
|
// Get the local transport properties
|
||||||
Map<TransportId, TransportProperties> localProperties, remoteProperties;
|
Map<TransportId, TransportProperties> localProperties;
|
||||||
try {
|
try {
|
||||||
localProperties = transportPropertyManager.getLocalProperties();
|
localProperties = transportPropertyManager.getLocalProperties();
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
listener.contactExchangeFailed();
|
listener.contactExchangeFailed();
|
||||||
tryToClose(conn, true);
|
tryToClose(conn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,158 +148,138 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
streamReaderFactory.createContactExchangeStreamReader(in,
|
streamReaderFactory.createContactExchangeStreamReader(in,
|
||||||
alice ? bobHeaderKey : aliceHeaderKey);
|
alice ? bobHeaderKey : aliceHeaderKey);
|
||||||
BdfReader r = bdfReaderFactory.createReader(streamReader);
|
RecordReader recordReader =
|
||||||
|
recordReaderFactory.createRecordReader(streamReader);
|
||||||
|
|
||||||
// Create the writers
|
// Create the writers
|
||||||
OutputStream streamWriter =
|
OutputStream streamWriter =
|
||||||
streamWriterFactory.createContactExchangeStreamWriter(out,
|
streamWriterFactory.createContactExchangeStreamWriter(out,
|
||||||
alice ? aliceHeaderKey : bobHeaderKey);
|
alice ? aliceHeaderKey : bobHeaderKey);
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
RecordWriter recordWriter =
|
||||||
|
recordWriterFactory.createRecordWriter(streamWriter);
|
||||||
|
|
||||||
// Derive the nonces to be signed
|
// Derive the nonces to be signed
|
||||||
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
|
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
|
||||||
new byte[] {PROTOCOL_VERSION});
|
new byte[] {PROTOCOL_VERSION});
|
||||||
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
|
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
|
||||||
new byte[] {PROTOCOL_VERSION});
|
new byte[] {PROTOCOL_VERSION});
|
||||||
|
byte[] localNonce = alice ? aliceNonce : bobNonce;
|
||||||
|
byte[] remoteNonce = alice ? bobNonce : aliceNonce;
|
||||||
|
|
||||||
// Exchange pseudonyms, signed nonces, and timestamps
|
// Sign the nonce
|
||||||
|
byte[] localSignature = sign(localAuthor, localNonce);
|
||||||
|
|
||||||
|
// Exchange contact info
|
||||||
long localTimestamp = clock.currentTimeMillis();
|
long localTimestamp = clock.currentTimeMillis();
|
||||||
Author remoteAuthor;
|
ContactInfo remoteInfo;
|
||||||
long remoteTimestamp;
|
|
||||||
try {
|
try {
|
||||||
if (alice) {
|
if (alice) {
|
||||||
sendPseudonym(w, aliceNonce);
|
sendContactInfo(recordWriter, localAuthor, localProperties,
|
||||||
sendTimestamp(w, localTimestamp);
|
localSignature, localTimestamp);
|
||||||
sendTransportProperties(w, localProperties);
|
recordWriter.flush();
|
||||||
w.flush();
|
remoteInfo = receiveContactInfo(recordReader);
|
||||||
remoteAuthor = receivePseudonym(r, bobNonce);
|
|
||||||
remoteTimestamp = receiveTimestamp(r);
|
|
||||||
remoteProperties = receiveTransportProperties(r);
|
|
||||||
} else {
|
} else {
|
||||||
remoteAuthor = receivePseudonym(r, aliceNonce);
|
remoteInfo = receiveContactInfo(recordReader);
|
||||||
remoteTimestamp = receiveTimestamp(r);
|
sendContactInfo(recordWriter, localAuthor, localProperties,
|
||||||
remoteProperties = receiveTransportProperties(r);
|
localSignature, localTimestamp);
|
||||||
sendPseudonym(w, bobNonce);
|
recordWriter.flush();
|
||||||
sendTimestamp(w, localTimestamp);
|
|
||||||
sendTransportProperties(w, localProperties);
|
|
||||||
w.flush();
|
|
||||||
}
|
}
|
||||||
// Close the outgoing stream and expect EOF on the incoming stream
|
// Close the outgoing stream
|
||||||
w.close();
|
recordWriter.close();
|
||||||
if (!r.eof()) LOG.warning("Unexpected data at end of connection");
|
// Skip any remaining records from the incoming stream
|
||||||
} catch (GeneralSecurityException | IOException e) {
|
try {
|
||||||
|
while (true) recordReader.readRecord();
|
||||||
|
} catch (EOFException expected) {
|
||||||
|
LOG.info("End of stream");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
listener.contactExchangeFailed();
|
listener.contactExchangeFailed();
|
||||||
tryToClose(conn, true);
|
tryToClose(conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the contact's signature
|
||||||
|
if (!verify(remoteInfo.author, remoteNonce, remoteInfo.signature)) {
|
||||||
|
LOG.warning("Invalid signature");
|
||||||
|
listener.contactExchangeFailed();
|
||||||
|
tryToClose(conn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The agreed timestamp is the minimum of the peers' timestamps
|
// The agreed timestamp is the minimum of the peers' timestamps
|
||||||
long timestamp = Math.min(localTimestamp, remoteTimestamp);
|
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Add the contact
|
// Add the contact
|
||||||
ContactId contactId = addContact(remoteAuthor, timestamp,
|
ContactId contactId = addContact(remoteInfo.author, timestamp,
|
||||||
remoteProperties);
|
remoteInfo.properties);
|
||||||
// Reuse the connection as a transport connection
|
// Reuse the connection as a transport connection
|
||||||
connectionManager.manageOutgoingConnection(contactId, transportId,
|
connectionManager.manageOutgoingConnection(contactId, transportId,
|
||||||
conn);
|
conn);
|
||||||
// Pseudonym exchange succeeded
|
// Pseudonym exchange succeeded
|
||||||
LOG.info("Pseudonym exchange succeeded");
|
LOG.info("Pseudonym exchange succeeded");
|
||||||
listener.contactExchangeSucceeded(remoteAuthor);
|
listener.contactExchangeSucceeded(remoteInfo.author);
|
||||||
} catch (ContactExistsException e) {
|
} catch (ContactExistsException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
tryToClose(conn, true);
|
tryToClose(conn);
|
||||||
listener.duplicateContact(remoteAuthor);
|
listener.duplicateContact(remoteInfo.author);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
tryToClose(conn, true);
|
tryToClose(conn);
|
||||||
listener.contactExchangeFailed();
|
listener.contactExchangeFailed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendPseudonym(BdfWriter w, byte[] nonce)
|
private byte[] sign(LocalAuthor author, byte[] nonce) {
|
||||||
throws GeneralSecurityException, IOException {
|
try {
|
||||||
// Sign the nonce
|
return crypto.sign(SIGNING_LABEL_EXCHANGE, nonce,
|
||||||
byte[] privateKey = localAuthor.getPrivateKey();
|
author.getPrivateKey());
|
||||||
byte[] sig = crypto.sign(SIGNING_LABEL_EXCHANGE, nonce, privateKey);
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new AssertionError();
|
||||||
// Write the name, public key and signature
|
|
||||||
w.writeListStart();
|
|
||||||
w.writeLong(localAuthor.getFormatVersion());
|
|
||||||
w.writeString(localAuthor.getName());
|
|
||||||
w.writeRaw(localAuthor.getPublicKey());
|
|
||||||
w.writeRaw(sig);
|
|
||||||
w.writeListEnd();
|
|
||||||
LOG.info("Sent pseudonym");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Author receivePseudonym(BdfReader r, byte[] nonce)
|
|
||||||
throws GeneralSecurityException, IOException {
|
|
||||||
// Read the format version, name, public key and signature
|
|
||||||
r.readListStart();
|
|
||||||
int formatVersion = (int) r.readLong();
|
|
||||||
if (formatVersion != FORMAT_VERSION) throw new FormatException();
|
|
||||||
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
|
|
||||||
if (name.isEmpty()) throw new FormatException();
|
|
||||||
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
|
||||||
if (publicKey.length == 0) throw new FormatException();
|
|
||||||
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
|
|
||||||
if (sig.length == 0) throw new FormatException();
|
|
||||||
r.readListEnd();
|
|
||||||
LOG.info("Received pseudonym");
|
|
||||||
// Verify the signature
|
|
||||||
if (!crypto.verify(SIGNING_LABEL_EXCHANGE, nonce, publicKey, sig)) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Invalid signature");
|
|
||||||
throw new GeneralSecurityException();
|
|
||||||
}
|
}
|
||||||
return authorFactory.createAuthor(formatVersion, name, publicKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendTimestamp(BdfWriter w, long timestamp)
|
private boolean verify(Author author, byte[] nonce, byte[] signature) {
|
||||||
|
try {
|
||||||
|
return crypto.verifySignature(signature, SIGNING_LABEL_EXCHANGE,
|
||||||
|
nonce, author.getPublicKey());
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendContactInfo(RecordWriter recordWriter, Author author,
|
||||||
|
Map<TransportId, TransportProperties> properties, byte[] signature,
|
||||||
|
long timestamp) throws IOException {
|
||||||
|
BdfList authorList = clientHelper.toList(author);
|
||||||
|
BdfDictionary props = clientHelper.toDictionary(properties);
|
||||||
|
BdfList payload = BdfList.of(authorList, props, signature, timestamp);
|
||||||
|
recordWriter.writeRecord(new Record(PROTOCOL_VERSION, CONTACT_INFO,
|
||||||
|
clientHelper.toByteArray(payload)));
|
||||||
|
LOG.info("Sent contact info");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContactInfo receiveContactInfo(RecordReader recordReader)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
w.writeLong(timestamp);
|
Record record;
|
||||||
LOG.info("Sent timestamp");
|
do {
|
||||||
}
|
record = recordReader.readRecord();
|
||||||
|
if (record.getProtocolVersion() != PROTOCOL_VERSION)
|
||||||
private long receiveTimestamp(BdfReader r) throws IOException {
|
throw new FormatException();
|
||||||
long timestamp = r.readLong();
|
} while (record.getRecordType() != CONTACT_INFO);
|
||||||
|
LOG.info("Received contact info");
|
||||||
|
BdfList payload = clientHelper.toList(record.getPayload());
|
||||||
|
checkSize(payload, 4);
|
||||||
|
Author author = clientHelper.parseAndValidateAuthor(payload.getList(0));
|
||||||
|
BdfDictionary props = payload.getDictionary(1);
|
||||||
|
Map<TransportId, TransportProperties> properties =
|
||||||
|
clientHelper.parseAndValidateTransportPropertiesMap(props);
|
||||||
|
byte[] signature = payload.getRaw(2);
|
||||||
|
checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
|
||||||
|
long timestamp = payload.getLong(3);
|
||||||
if (timestamp < 0) throw new FormatException();
|
if (timestamp < 0) throw new FormatException();
|
||||||
LOG.info("Received timestamp");
|
return new ContactInfo(author, properties, signature, timestamp);
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendTransportProperties(BdfWriter w,
|
|
||||||
Map<TransportId, TransportProperties> local) throws IOException {
|
|
||||||
w.writeListStart();
|
|
||||||
for (Entry<TransportId, TransportProperties> e : local.entrySet())
|
|
||||||
w.writeList(BdfList.of(e.getKey().getString(), e.getValue()));
|
|
||||||
w.writeListEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<TransportId, TransportProperties> receiveTransportProperties(
|
|
||||||
BdfReader r) throws IOException {
|
|
||||||
Map<TransportId, TransportProperties> remote = new HashMap<>();
|
|
||||||
r.readListStart();
|
|
||||||
while (!r.hasListEnd()) {
|
|
||||||
r.readListStart();
|
|
||||||
String id = r.readString(MAX_TRANSPORT_ID_LENGTH);
|
|
||||||
if (id.isEmpty()) throw new FormatException();
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
r.readDictionaryStart();
|
|
||||||
while (!r.hasDictionaryEnd()) {
|
|
||||||
if (p.size() == MAX_PROPERTIES_PER_TRANSPORT)
|
|
||||||
throw new FormatException();
|
|
||||||
String key = r.readString(MAX_PROPERTY_LENGTH);
|
|
||||||
String value = r.readString(MAX_PROPERTY_LENGTH);
|
|
||||||
p.put(key, value);
|
|
||||||
}
|
|
||||||
r.readDictionaryEnd();
|
|
||||||
r.readListEnd();
|
|
||||||
remote.put(new TransportId(id), p);
|
|
||||||
}
|
|
||||||
r.readListEnd();
|
|
||||||
return remote;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId addContact(Author remoteAuthor, long timestamp,
|
private ContactId addContact(Author remoteAuthor, long timestamp,
|
||||||
@@ -323,13 +300,30 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return contactId;
|
return contactId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
private void tryToClose(DuplexTransportConnection conn) {
|
||||||
try {
|
try {
|
||||||
LOG.info("Closing connection");
|
LOG.info("Closing connection");
|
||||||
conn.getReader().dispose(exception, true);
|
conn.getReader().dispose(true, true);
|
||||||
conn.getWriter().dispose(exception);
|
conn.getWriter().dispose(true);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ContactInfo {
|
||||||
|
|
||||||
|
private final Author author;
|
||||||
|
private final Map<TransportId, TransportProperties> properties;
|
||||||
|
private final byte[] signature;
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
private ContactInfo(Author author,
|
||||||
|
Map<TransportId, TransportProperties> properties,
|
||||||
|
byte[] signature, long timestamp) {
|
||||||
|
this.author = author;
|
||||||
|
this.properties = properties;
|
||||||
|
this.signature = signature;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,36 +27,37 @@ class ContactManagerImpl implements ContactManager {
|
|||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final KeyManager keyManager;
|
private final KeyManager keyManager;
|
||||||
private final List<AddContactHook> addHooks;
|
private final List<ContactHook> hooks;
|
||||||
private final List<RemoveContactHook> removeHooks;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
|
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.keyManager = keyManager;
|
this.keyManager = keyManager;
|
||||||
addHooks = new CopyOnWriteArrayList<>();
|
hooks = new CopyOnWriteArrayList<>();
|
||||||
removeHooks = new CopyOnWriteArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerAddContactHook(AddContactHook hook) {
|
public void registerContactHook(ContactHook hook) {
|
||||||
addHooks.add(hook);
|
hooks.add(hook);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerRemoveContactHook(RemoveContactHook hook) {
|
|
||||||
removeHooks.add(hook);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
|
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 {
|
boolean active) throws DbException {
|
||||||
ContactId c = db.addContact(txn, remote, local, verified, active);
|
ContactId c = db.addContact(txn, remote, local, verified, active);
|
||||||
keyManager.addContact(txn, c, master, timestamp, alice);
|
keyManager.addContact(txn, c, master, timestamp, alice);
|
||||||
Contact contact = db.getContact(txn, c);
|
Contact contact = db.getContact(txn, c);
|
||||||
for (AddContactHook hook : addHooks)
|
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
|
||||||
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);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ class ContactManagerImpl implements ContactManager {
|
|||||||
@Override
|
@Override
|
||||||
public boolean contactExists(AuthorId remoteAuthorId,
|
public boolean contactExists(AuthorId remoteAuthorId,
|
||||||
AuthorId localAuthorId) throws DbException {
|
AuthorId localAuthorId) throws DbException {
|
||||||
boolean exists = false;
|
boolean exists;
|
||||||
Transaction txn = db.startTransaction(true);
|
Transaction txn = db.startTransaction(true);
|
||||||
try {
|
try {
|
||||||
exists = contactExists(txn, remoteAuthorId, localAuthorId);
|
exists = contactExists(txn, remoteAuthorId, localAuthorId);
|
||||||
@@ -171,8 +172,7 @@ class ContactManagerImpl implements ContactManager {
|
|||||||
public void removeContact(Transaction txn, ContactId c)
|
public void removeContact(Transaction txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Contact contact = db.getContact(txn, c);
|
Contact contact = db.getContact(txn, c);
|
||||||
for (RemoveContactHook hook : removeHooks)
|
for (ContactHook hook : hooks) hook.removingContact(txn, contact);
|
||||||
hook.removingContact(txn, contact);
|
|
||||||
db.removeContact(txn, c);
|
db.removeContact(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,12 +205,12 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verify(String label, byte[] signedData, byte[] publicKey,
|
public boolean verifySignature(byte[] signature, String label,
|
||||||
byte[] signature) throws GeneralSecurityException {
|
byte[] signed, byte[] publicKey) throws GeneralSecurityException {
|
||||||
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
|
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
|
||||||
Signature sig = new EdSignature();
|
Signature sig = new EdSignature();
|
||||||
sig.initVerify(key);
|
sig.initVerify(key);
|
||||||
updateSignature(sig, label, signedData);
|
updateSignature(sig, label, signed);
|
||||||
return sig.verify(signature);
|
return sig.verify(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,6 +262,17 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return output;
|
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
|
@Override
|
||||||
public byte[] encryptWithPassword(byte[] input, String password) {
|
public byte[] encryptWithPassword(byte[] input, String password) {
|
||||||
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.RejectedExecutionHandler;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class CryptoExecutorModule {
|
||||||
|
|
||||||
|
public static class EagerSingletons {
|
||||||
|
@Inject
|
||||||
|
@CryptoExecutor
|
||||||
|
ExecutorService cryptoExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of executor threads.
|
||||||
|
* <p>
|
||||||
|
* The number of available processors can change during the lifetime of the
|
||||||
|
* JVM, so this is just a reasonable guess.
|
||||||
|
*/
|
||||||
|
private static final int MAX_EXECUTOR_THREADS =
|
||||||
|
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||||
|
|
||||||
|
private final ExecutorService cryptoExecutor;
|
||||||
|
|
||||||
|
public CryptoExecutorModule() {
|
||||||
|
// Use an unbounded queue
|
||||||
|
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||||
|
// Discard tasks that are submitted during shutdown
|
||||||
|
RejectedExecutionHandler policy =
|
||||||
|
new ThreadPoolExecutor.DiscardPolicy();
|
||||||
|
// Create a limited # of threads and keep them in the pool for 60 secs
|
||||||
|
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
|
||||||
|
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@CryptoExecutor
|
||||||
|
ExecutorService provideCryptoExecutorService(
|
||||||
|
LifecycleManager lifecycleManager) {
|
||||||
|
lifecycleManager.registerForShutdown(cryptoExecutor);
|
||||||
|
return cryptoExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@CryptoExecutor
|
||||||
|
Executor provideCryptoExecutor() {
|
||||||
|
return cryptoExecutor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,64 +1,24 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.RejectedExecutionHandler;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class CryptoModule {
|
public class CryptoModule {
|
||||||
|
|
||||||
public static class EagerSingletons {
|
|
||||||
@Inject
|
|
||||||
@CryptoExecutor
|
|
||||||
ExecutorService cryptoExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum number of executor threads.
|
|
||||||
* <p>
|
|
||||||
* The number of available processors can change during the lifetime of the
|
|
||||||
* JVM, so this is just a reasonable guess.
|
|
||||||
*/
|
|
||||||
private static final int MAX_EXECUTOR_THREADS =
|
|
||||||
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
|
||||||
|
|
||||||
private final ExecutorService cryptoExecutor;
|
|
||||||
|
|
||||||
public CryptoModule() {
|
|
||||||
// Use an unbounded queue
|
|
||||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
|
||||||
// Discard tasks that are submitted during shutdown
|
|
||||||
RejectedExecutionHandler policy =
|
|
||||||
new ThreadPoolExecutor.DiscardPolicy();
|
|
||||||
// Create a limited # of threads and keep them in the pool for 60 secs
|
|
||||||
cryptoExecutor = new TimeLoggingExecutor("CryptoExecutor", 0,
|
|
||||||
MAX_EXECUTOR_THREADS, 60, SECONDS, queue, policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
AuthenticatedCipher provideAuthenticatedCipher() {
|
AuthenticatedCipher provideAuthenticatedCipher() {
|
||||||
return new XSalsa20Poly1305AuthenticatedCipher();
|
return new XSalsa20Poly1305AuthenticatedCipher();
|
||||||
@@ -103,21 +63,6 @@ public class CryptoModule {
|
|||||||
return keyAgreementCrypto;
|
return keyAgreementCrypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@CryptoExecutor
|
|
||||||
ExecutorService getCryptoExecutorService(
|
|
||||||
LifecycleManager lifecycleManager) {
|
|
||||||
lifecycleManager.registerForShutdown(cryptoExecutor);
|
|
||||||
return cryptoExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@CryptoExecutor
|
|
||||||
Executor getCryptoExecutor() {
|
|
||||||
return cryptoExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
SecureRandom getSecureRandom(CryptoComponent crypto) {
|
SecureRandom getSecureRandom(CryptoComponent crypto) {
|
||||||
return crypto.getSecureRandom();
|
return crypto.getSecureRandom();
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class TransportCryptoImpl implements TransportCrypto {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransportKeys deriveTransportKeys(TransportId t,
|
public TransportKeys deriveTransportKeys(TransportId t,
|
||||||
SecretKey master, long rotationPeriod, boolean alice) {
|
SecretKey master, long rotationPeriod, boolean alice,
|
||||||
|
boolean active) {
|
||||||
// Keys for the previous period are derived from the master secret
|
// Keys for the previous period are derived from the master secret
|
||||||
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
||||||
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
||||||
@@ -57,7 +58,7 @@ class TransportCryptoImpl implements TransportCrypto {
|
|||||||
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
||||||
rotationPeriod + 1);
|
rotationPeriod + 1);
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
||||||
rotationPeriod);
|
rotationPeriod, active);
|
||||||
// Collect and return the keys
|
// Collect and return the keys
|
||||||
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
||||||
}
|
}
|
||||||
@@ -71,6 +72,7 @@ class TransportCryptoImpl implements TransportCrypto {
|
|||||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
long startPeriod = outCurr.getRotationPeriod();
|
long startPeriod = outCurr.getRotationPeriod();
|
||||||
|
boolean active = outCurr.isActive();
|
||||||
// Rotate the keys
|
// Rotate the keys
|
||||||
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
||||||
inPrev = inCurr;
|
inPrev = inCurr;
|
||||||
@@ -80,7 +82,7 @@ class TransportCryptoImpl implements TransportCrypto {
|
|||||||
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
||||||
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
||||||
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
||||||
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p, active);
|
||||||
}
|
}
|
||||||
// Collect and return the keys
|
// Collect and return the keys
|
||||||
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import org.briarproject.bramble.api.sync.Message;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
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 org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -105,10 +107,11 @@ interface Database<T> {
|
|||||||
@Nullable ContactId sender) throws DbException;
|
@Nullable ContactId sender) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a dependency between two messages in the given group.
|
* Adds a dependency between two messages, where the dependent message is
|
||||||
|
* in the given state.
|
||||||
*/
|
*/
|
||||||
void addMessageDependency(T txn, GroupId g, MessageId dependent,
|
void addMessageDependency(T txn, Message dependent, MessageId dependency,
|
||||||
MessageId dependency) throws DbException;
|
State dependentState) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records that a message has been offered by the given contact.
|
* Records that a message has been offered by the given contact.
|
||||||
@@ -122,9 +125,16 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores transport keys for a newly added contact.
|
* Stores the given transport keys, optionally binding them to the given
|
||||||
|
* contact, and returns a key set ID.
|
||||||
*/
|
*/
|
||||||
void addTransportKeys(T txn, ContactId c, TransportKeys k)
|
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)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,7 +266,8 @@ interface Database<T> {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<Group> getGroups(T txn, ClientId c) throws DbException;
|
Collection<Group> getGroups(T txn, ClientId c, int majorVersion)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given group's visibility to the given contact, or
|
* Returns the given group's visibility to the given contact, or
|
||||||
@@ -292,10 +303,8 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs and states of all dependencies of the given message.
|
* Returns the IDs and states of all dependencies of the given message.
|
||||||
* Missing dependencies have the state {@link State UNKNOWN}.
|
* For missing dependencies and dependencies in other groups, the state
|
||||||
* Dependencies in other groups have the state {@link State INVALID}.
|
* {@link State UNKNOWN} is returned.
|
||||||
* 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/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
@@ -303,9 +312,9 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all IDs and states of all dependents of the given message.
|
* Returns the IDs and states of all dependents of the given message.
|
||||||
* Messages in other groups that declare a dependency on the given message
|
* Dependents in other groups are not returned. If the given message is
|
||||||
* will be returned even though such dependencies are invalid.
|
* missing, no dependents are returned.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
@@ -487,15 +496,14 @@ interface Database<T> {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Map<ContactId, TransportKeys> getTransportKeys(T txn, TransportId t)
|
Collection<KeySet> getTransportKeys(T txn, TransportId t)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the outgoing stream counter for the given contact and
|
* Increments the outgoing stream counter for the given transport keys.
|
||||||
* transport in the given rotation period.
|
|
||||||
*/
|
*/
|
||||||
void incrementStreamCounter(T txn, ContactId c, TransportId t,
|
void incrementStreamCounter(T txn, TransportId t, KeySetId k)
|
||||||
long rotationPeriod) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given messages as not needing to be acknowledged to the
|
* Marks the given messages as not needing to be acknowledged to the
|
||||||
@@ -585,6 +593,12 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void removeTransport(T txn, TransportId t) throws DbException;
|
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
|
* Resets the transmission count and expiry time of the given message with
|
||||||
* respect to the given contact.
|
* respect to the given contact.
|
||||||
@@ -620,12 +634,18 @@ interface Database<T> {
|
|||||||
void setMessageState(T txn, MessageId m, State state) throws DbException;
|
void setMessageState(T txn, MessageId m, State state) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the reordering window for the given contact and transport in the
|
* Sets the reordering window for the given key set and transport in the
|
||||||
* given rotation period.
|
* given rotation period.
|
||||||
*/
|
*/
|
||||||
void setReorderingWindow(T txn, ContactId c, TransportId t,
|
void setReorderingWindow(T txn, KeySetId k, TransportId t,
|
||||||
long rotationPeriod, long base, byte[] bitmap) throws DbException;
|
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
|
* Updates the transmission count and expiry time of the given message
|
||||||
* with respect to the given contact, using the latency of the transport
|
* with respect to the given contact, using the latency of the transport
|
||||||
@@ -635,8 +655,7 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given transport keys, deleting any keys they have replaced.
|
* Updates the given transport keys following key rotation.
|
||||||
*/
|
*/
|
||||||
void updateTransportKeys(T txn, Map<ContactId, TransportKeys> keys)
|
void updateTransportKeys(T txn, KeySet ks) throws DbException;
|
||||||
throws DbException;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,15 +51,15 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
|||||||
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
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 org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -234,15 +234,27 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTransportKeys(Transaction transaction, ContactId c,
|
public KeySetId addTransportKeys(Transaction transaction,
|
||||||
TransportKeys k) throws DbException {
|
@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 {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
if (!db.containsTransport(txn, k.getTransportId()))
|
if (!db.containsTransport(txn, t))
|
||||||
throw new NoSuchTransportException();
|
throw new NoSuchTransportException();
|
||||||
db.addTransportKeys(txn, c, k);
|
db.bindTransportKeys(txn, c, t, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -423,10 +435,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Group> getGroups(Transaction transaction, ClientId c)
|
public Collection<Group> getGroups(Transaction transaction, ClientId c,
|
||||||
throws DbException {
|
int majorVersion) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getGroups(txn, c);
|
return db.getGroups(txn, c, majorVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -454,6 +466,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getLocalAuthors(txn);
|
return db.getLocalAuthors(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<MessageId> getMessageIds(Transaction transaction,
|
||||||
|
GroupId g) throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsGroup(txn, g))
|
||||||
|
throw new NoSuchGroupException();
|
||||||
|
return db.getMessageIds(txn, g);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -586,8 +607,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<ContactId, TransportKeys> getTransportKeys(
|
public Collection<KeySet> getTransportKeys(Transaction transaction,
|
||||||
Transaction transaction, TransportId t) throws DbException {
|
TransportId t) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsTransport(txn, t))
|
if (!db.containsTransport(txn, t))
|
||||||
throw new NoSuchTransportException();
|
throw new NoSuchTransportException();
|
||||||
@@ -595,15 +616,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementStreamCounter(Transaction transaction, ContactId c,
|
public void incrementStreamCounter(Transaction transaction, TransportId t,
|
||||||
TransportId t, long rotationPeriod) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
if (!db.containsTransport(txn, t))
|
if (!db.containsTransport(txn, t))
|
||||||
throw new NoSuchTransportException();
|
throw new NoSuchTransportException();
|
||||||
db.incrementStreamCounter(txn, c, t, rotationPeriod);
|
db.incrementStreamCounter(txn, t, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -765,6 +784,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsMessage(txn, m))
|
if (!db.containsMessage(txn, m))
|
||||||
throw new NoSuchMessageException();
|
throw new NoSuchMessageException();
|
||||||
|
// TODO: Don't allow messages with dependents to be removed
|
||||||
db.removeMessage(txn, m);
|
db.removeMessage(txn, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,6 +798,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.removeTransport(txn, t);
|
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
|
@Override
|
||||||
public void setContactVerified(Transaction transaction, ContactId c)
|
public void setContactVerified(Transaction transaction, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -850,39 +880,41 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsMessage(txn, dependent.getId()))
|
if (!db.containsMessage(txn, dependent.getId()))
|
||||||
throw new NoSuchMessageException();
|
throw new NoSuchMessageException();
|
||||||
|
State dependentState = db.getMessageState(txn, dependent.getId());
|
||||||
for (MessageId dependency : dependencies) {
|
for (MessageId dependency : dependencies) {
|
||||||
db.addMessageDependency(txn, dependent.getGroupId(),
|
db.addMessageDependency(txn, dependent, dependency, dependentState);
|
||||||
dependent.getId(), dependency);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setReorderingWindow(Transaction transaction, ContactId c,
|
public void setReorderingWindow(Transaction transaction, KeySetId k,
|
||||||
TransportId t, long rotationPeriod, long base, byte[] bitmap)
|
TransportId t, long rotationPeriod, long base, byte[] bitmap)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
|
||||||
throw new NoSuchContactException();
|
|
||||||
if (!db.containsTransport(txn, t))
|
if (!db.containsTransport(txn, t))
|
||||||
throw new NoSuchTransportException();
|
throw new NoSuchTransportException();
|
||||||
db.setReorderingWindow(txn, c, t, rotationPeriod, base, bitmap);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateTransportKeys(Transaction transaction,
|
public void updateTransportKeys(Transaction transaction,
|
||||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
Collection<KeySet> keys) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
Map<ContactId, TransportKeys> filtered = new HashMap<>();
|
for (KeySet ks : keys) {
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
TransportId t = ks.getTransportKeys().getTransportId();
|
||||||
ContactId c = e.getKey();
|
if (db.containsTransport(txn, t)) db.updateTransportKeys(txn, ks);
|
||||||
TransportKeys k = e.getValue();
|
|
||||||
if (db.containsContact(txn, c)
|
|
||||||
&& db.containsTransport(txn, k.getTransportId())) {
|
|
||||||
filtered.put(c, k);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
db.updateTransportKeys(txn, filtered);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import org.briarproject.bramble.api.sync.MessageStatus;
|
|||||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
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.OutgoingKeys;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.sql.Types.INTEGER;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
@@ -57,7 +60,6 @@ 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.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
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.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.PENDING;
|
||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||||
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
|
||||||
@@ -72,7 +74,12 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
|||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final int CODE_SCHEMA_VERSION = 35;
|
static final int CODE_SCHEMA_VERSION = 38;
|
||||||
|
|
||||||
|
// Rotation period offsets for incoming transport keys
|
||||||
|
private static final int OFFSET_PREV = -1;
|
||||||
|
private static final int OFFSET_CURR = 0;
|
||||||
|
private static final int OFFSET_NEXT = 1;
|
||||||
|
|
||||||
private static final String CREATE_SETTINGS =
|
private static final String CREATE_SETTINGS =
|
||||||
"CREATE TABLE settings"
|
"CREATE TABLE settings"
|
||||||
@@ -110,6 +117,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"CREATE TABLE groups"
|
"CREATE TABLE groups"
|
||||||
+ " (groupId _HASH NOT NULL,"
|
+ " (groupId _HASH NOT NULL,"
|
||||||
+ " clientId _STRING NOT NULL,"
|
+ " clientId _STRING NOT NULL,"
|
||||||
|
+ " majorVersion INT NOT NULL,"
|
||||||
+ " descriptor _BINARY NOT NULL,"
|
+ " descriptor _BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (groupId))";
|
+ " PRIMARY KEY (groupId))";
|
||||||
|
|
||||||
@@ -170,6 +178,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " (groupId _HASH NOT NULL,"
|
+ " (groupId _HASH NOT NULL,"
|
||||||
+ " messageId _HASH NOT NULL,"
|
+ " messageId _HASH NOT NULL,"
|
||||||
+ " dependencyId _HASH NOT NULL," // Not a foreign key
|
+ " 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)"
|
+ " FOREIGN KEY (groupId)"
|
||||||
+ " REFERENCES groups (groupId)"
|
+ " REFERENCES groups (groupId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -219,51 +231,63 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " maxLatency INT NOT NULL,"
|
+ " maxLatency INT NOT NULL,"
|
||||||
+ " PRIMARY KEY (transportId))";
|
+ " 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 =
|
private static final String CREATE_INCOMING_KEYS =
|
||||||
"CREATE TABLE incomingKeys"
|
"CREATE TABLE incomingKeys"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (transportId _STRING NOT NULL,"
|
||||||
+ " transportId _STRING NOT NULL,"
|
+ " keySetId INT NOT NULL,"
|
||||||
+ " rotationPeriod BIGINT NOT NULL,"
|
+ " rotationPeriod BIGINT NOT NULL,"
|
||||||
|
+ " contactId INT," // Null if keys are not bound
|
||||||
+ " tagKey _SECRET NOT NULL,"
|
+ " tagKey _SECRET NOT NULL,"
|
||||||
+ " headerKey _SECRET NOT NULL,"
|
+ " headerKey _SECRET NOT NULL,"
|
||||||
+ " base BIGINT NOT NULL,"
|
+ " base BIGINT NOT NULL,"
|
||||||
+ " bitmap _BINARY NOT NULL,"
|
+ " bitmap _BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, transportId, rotationPeriod),"
|
+ " periodOffset INT NOT NULL,"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " PRIMARY KEY (transportId, keySetId, periodOffset),"
|
||||||
+ " REFERENCES contacts (contactId)"
|
|
||||||
+ " ON DELETE CASCADE,"
|
|
||||||
+ " FOREIGN KEY (transportId)"
|
+ " FOREIGN KEY (transportId)"
|
||||||
+ " REFERENCES transports (transportId)"
|
+ " REFERENCES transports (transportId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE,"
|
||||||
|
+ " FOREIGN KEY (keySetId)"
|
||||||
private static final String CREATE_OUTGOING_KEYS =
|
+ " REFERENCES outgoingKeys (keySetId)"
|
||||||
"CREATE TABLE outgoingKeys"
|
+ " ON DELETE CASCADE,"
|
||||||
+ " (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)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
+ " REFERENCES contacts (contactId)"
|
+ " REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE,"
|
|
||||||
+ " FOREIGN KEY (transportId)"
|
|
||||||
+ " REFERENCES transports (transportId)"
|
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String INDEX_CONTACTS_BY_AUTHOR_ID =
|
private static final String INDEX_CONTACTS_BY_AUTHOR_ID =
|
||||||
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
||||||
+ " ON contacts (authorId)";
|
+ " ON contacts (authorId)";
|
||||||
|
|
||||||
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
private static final String INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION =
|
||||||
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
"CREATE INDEX IF NOT EXISTS groupsByClientIdMajorVersion"
|
||||||
+ " ON groups (clientId)";
|
+ " ON groups (clientId, majorVersion)";
|
||||||
|
|
||||||
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
||||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||||
+ " ON messageMetadata (groupId, state)";
|
+ " 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 =
|
private static final String INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID =
|
||||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdGroupId"
|
"CREATE INDEX IF NOT EXISTS statusesByContactIdGroupId"
|
||||||
+ " ON statuses (contactId, groupId)";
|
+ " ON statuses (contactId, groupId)";
|
||||||
@@ -407,8 +431,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(insertTypeNames(CREATE_OFFERS));
|
s.executeUpdate(insertTypeNames(CREATE_OFFERS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_STATUSES));
|
s.executeUpdate(insertTypeNames(CREATE_STATUSES));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS));
|
s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
|
|
||||||
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
|
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
|
||||||
|
s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -421,8 +445,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
s = txn.createStatement();
|
s = txn.createStatement();
|
||||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID_MAJOR_VERSION);
|
||||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
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_GROUP_ID);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||||
s.close();
|
s.close();
|
||||||
@@ -588,12 +613,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
public void addGroup(Connection txn, Group g) throws DbException {
|
public void addGroup(Connection txn, Group g) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "INSERT INTO groups (groupId, clientId, descriptor)"
|
String sql = "INSERT INTO groups"
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " (groupId, clientId, majorVersion, descriptor)"
|
||||||
|
+ " VALUES (?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getId().getBytes());
|
ps.setBytes(1, g.getId().getBytes());
|
||||||
ps.setString(2, g.getClientId().getString());
|
ps.setString(2, g.getClientId().getString());
|
||||||
ps.setBytes(3, g.getDescriptor());
|
ps.setInt(3, g.getMajorVersion());
|
||||||
|
ps.setBytes(4, g.getDescriptor());
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -715,6 +742,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
m.getLength(), state, e.getValue(), messageShared,
|
m.getLength(), state, e.getValue(), messageShared,
|
||||||
false, seen);
|
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) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -784,21 +822,42 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMessageDependency(Connection txn, GroupId g,
|
public void addMessageDependency(Connection txn, Message dependent,
|
||||||
MessageId dependent, MessageId dependency) throws DbException {
|
MessageId dependency, State dependentState) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "INSERT INTO messageDependencies"
|
// Get state of dependency if present and in same group as dependent
|
||||||
+ " (groupId, messageId, dependencyId)"
|
String sql = "SELECT state FROM messages"
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " WHERE messageId = ? AND groupId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setBytes(1, dependency.getBytes());
|
||||||
ps.setBytes(2, dependent.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(3, dependency.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();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
@@ -824,61 +883,109 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTransportKeys(Connection txn, ContactId c, TransportKeys k)
|
public KeySetId addTransportKeys(Connection txn, @Nullable ContactId c,
|
||||||
throws DbException {
|
TransportKeys k) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
// Store the incoming keys
|
// Store the outgoing keys
|
||||||
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
|
String sql = "INSERT INTO outgoingKeys (contactId, transportId,"
|
||||||
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
|
+ " rotationPeriod, tagKey, headerKey, stream, active)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
if (c == null) ps.setNull(1, INTEGER);
|
||||||
ps.setString(2, k.getTransportId().getString());
|
else ps.setInt(1, c.getInt());
|
||||||
// Previous rotation period
|
|
||||||
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
|
||||||
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(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(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();
|
|
||||||
// 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.setString(2, k.getTransportId().getString());
|
ps.setString(2, k.getTransportId().getString());
|
||||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
ps.setLong(3, outCurr.getRotationPeriod());
|
ps.setLong(3, outCurr.getRotationPeriod());
|
||||||
ps.setBytes(4, outCurr.getTagKey().getBytes());
|
ps.setBytes(4, outCurr.getTagKey().getBytes());
|
||||||
ps.setBytes(5, outCurr.getHeaderKey().getBytes());
|
ps.setBytes(5, outCurr.getHeaderKey().getBytes());
|
||||||
ps.setLong(6, outCurr.getStreamCounter());
|
ps.setLong(6, outCurr.getStreamCounter());
|
||||||
|
ps.setBoolean(7, outCurr.isActive());
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
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,"
|
||||||
|
+ " periodOffset)"
|
||||||
|
+ " 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.setInt(9, OFFSET_PREV);
|
||||||
|
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.setInt(9, OFFSET_CURR);
|
||||||
|
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.setInt(9, OFFSET_NEXT);
|
||||||
|
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 = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
ps.setInt(2, k.getInt());
|
||||||
|
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();
|
||||||
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -1242,17 +1349,18 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT clientId, descriptor FROM groups"
|
String sql = "SELECT clientId, majorVersion, descriptor"
|
||||||
+ " WHERE groupId = ?";
|
+ " FROM groups WHERE groupId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setBytes(1, g.getBytes());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
if (!rs.next()) throw new DbStateException();
|
if (!rs.next()) throw new DbStateException();
|
||||||
ClientId clientId = new ClientId(rs.getString(1));
|
ClientId clientId = new ClientId(rs.getString(1));
|
||||||
byte[] descriptor = rs.getBytes(2);
|
int majorVersion = rs.getInt(2);
|
||||||
|
byte[] descriptor = rs.getBytes(3);
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
return new Group(g, clientId, descriptor);
|
return new Group(g, clientId, majorVersion, descriptor);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs);
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
@@ -1261,21 +1369,22 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Group> getGroups(Connection txn, ClientId c)
|
public Collection<Group> getGroups(Connection txn, ClientId c,
|
||||||
throws DbException {
|
int majorVersion) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT groupId, descriptor FROM groups"
|
String sql = "SELECT groupId, descriptor FROM groups"
|
||||||
+ " WHERE clientId = ?";
|
+ " WHERE clientId = ? AND majorVersion = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, c.getString());
|
ps.setString(1, c.getString());
|
||||||
|
ps.setInt(2, majorVersion);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<Group> groups = new ArrayList<>();
|
List<Group> groups = new ArrayList<>();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
GroupId id = new GroupId(rs.getBytes(1));
|
GroupId id = new GroupId(rs.getBytes(1));
|
||||||
byte[] descriptor = rs.getBytes(2);
|
byte[] descriptor = rs.getBytes(2);
|
||||||
groups.add(new Group(id, c, descriptor));
|
groups.add(new Group(id, c, majorVersion, descriptor));
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -1663,11 +1772,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT d.dependencyId, m.state, d.groupId, m.groupId"
|
String sql = "SELECT dependencyId, dependencyState"
|
||||||
+ " FROM messageDependencies AS d"
|
+ " FROM messageDependencies"
|
||||||
+ " LEFT OUTER JOIN messages AS m"
|
+ " WHERE messageId = ?";
|
||||||
+ " ON d.dependencyId = m.messageId"
|
|
||||||
+ " WHERE d.messageId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getBytes());
|
ps.setBytes(1, m.getBytes());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1675,14 +1782,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
MessageId dependency = new MessageId(rs.getBytes(1));
|
MessageId dependency = new MessageId(rs.getBytes(1));
|
||||||
State state = State.fromValue(rs.getInt(2));
|
State state = State.fromValue(rs.getInt(2));
|
||||||
if (rs.wasNull()) {
|
if (rs.wasNull())
|
||||||
state = UNKNOWN; // Missing dependency
|
state = UNKNOWN; // Missing or in a different group
|
||||||
} 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);
|
dependencies.put(dependency, state);
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
@@ -1701,11 +1802,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT d.messageId, m.state"
|
// Exclude dependencies that are missing or in a different group
|
||||||
+ " FROM messageDependencies AS d"
|
// from the dependent
|
||||||
+ " JOIN messages AS m"
|
String sql = "SELECT messageId, messageState"
|
||||||
+ " ON d.messageId = m.messageId"
|
+ " FROM messageDependencies"
|
||||||
+ " WHERE dependencyId = ?";
|
+ " WHERE dependencyId = ?"
|
||||||
|
+ " AND dependencyState IS NOT NULL";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getBytes());
|
ps.setBytes(1, m.getBytes());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -2044,8 +2146,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<ContactId, TransportKeys> getTransportKeys(Connection txn,
|
public Collection<KeySet> getTransportKeys(Connection txn, TransportId t)
|
||||||
TransportId t) throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
@@ -2054,7 +2156,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " base, bitmap"
|
+ " base, bitmap"
|
||||||
+ " FROM incomingKeys"
|
+ " FROM incomingKeys"
|
||||||
+ " WHERE transportId = ?"
|
+ " WHERE transportId = ?"
|
||||||
+ " ORDER BY contactId, rotationPeriod";
|
+ " ORDER BY keySetId, periodOffset";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, t.getString());
|
ps.setString(1, t.getString());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -2071,29 +2173,34 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Retrieve the outgoing keys in the same order
|
// Retrieve the outgoing keys in the same order
|
||||||
sql = "SELECT contactId, rotationPeriod, tagKey, headerKey, stream"
|
sql = "SELECT keySetId, contactId, rotationPeriod,"
|
||||||
|
+ " tagKey, headerKey, stream, active"
|
||||||
+ " FROM outgoingKeys"
|
+ " FROM outgoingKeys"
|
||||||
+ " WHERE transportId = ?"
|
+ " WHERE transportId = ?"
|
||||||
+ " ORDER BY contactId, rotationPeriod";
|
+ " ORDER BY keySetId";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, t.getString());
|
ps.setString(1, t.getString());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
Map<ContactId, TransportKeys> keys = new HashMap<>();
|
Collection<KeySet> keys = new ArrayList<>();
|
||||||
for (int i = 0; rs.next(); i++) {
|
for (int i = 0; rs.next(); i++) {
|
||||||
// There should be three times as many incoming keys
|
// There should be three times as many incoming keys
|
||||||
if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
|
if (inKeys.size() < (i + 1) * 3) throw new DbStateException();
|
||||||
ContactId contactId = new ContactId(rs.getInt(1));
|
KeySetId keySetId = new KeySetId(rs.getInt(1));
|
||||||
long rotationPeriod = rs.getLong(2);
|
ContactId contactId = new ContactId(rs.getInt(2));
|
||||||
SecretKey tagKey = new SecretKey(rs.getBytes(3));
|
if (rs.wasNull()) contactId = null;
|
||||||
SecretKey headerKey = new SecretKey(rs.getBytes(4));
|
long rotationPeriod = rs.getLong(3);
|
||||||
long streamCounter = rs.getLong(5);
|
SecretKey tagKey = new SecretKey(rs.getBytes(4));
|
||||||
|
SecretKey headerKey = new SecretKey(rs.getBytes(5));
|
||||||
|
long streamCounter = rs.getLong(6);
|
||||||
|
boolean active = rs.getBoolean(7);
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
|
OutgoingKeys outCurr = new OutgoingKeys(tagKey, headerKey,
|
||||||
rotationPeriod, streamCounter);
|
rotationPeriod, streamCounter, active);
|
||||||
IncomingKeys inPrev = inKeys.get(i * 3);
|
IncomingKeys inPrev = inKeys.get(i * 3);
|
||||||
IncomingKeys inCurr = inKeys.get(i * 3 + 1);
|
IncomingKeys inCurr = inKeys.get(i * 3 + 1);
|
||||||
IncomingKeys inNext = inKeys.get(i * 3 + 2);
|
IncomingKeys inNext = inKeys.get(i * 3 + 2);
|
||||||
keys.put(contactId, new TransportKeys(t, inPrev, inCurr,
|
TransportKeys transportKeys = new TransportKeys(t, inPrev,
|
||||||
inNext, outCurr));
|
inCurr, inNext, outCurr);
|
||||||
|
keys.add(new KeySet(keySetId, contactId, transportKeys));
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2106,17 +2213,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementStreamCounter(Connection txn, ContactId c,
|
public void incrementStreamCounter(Connection txn, TransportId t,
|
||||||
TransportId t, long rotationPeriod) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
||||||
+ " WHERE contactId = ? AND transportId = ?"
|
+ " WHERE transportId = ? AND keySetId = ?";
|
||||||
+ " AND rotationPeriod = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setString(1, t.getString());
|
||||||
ps.setString(2, t.getString());
|
ps.setInt(2, k.getInt());
|
||||||
ps.setLong(3, rotationPeriod);
|
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2592,6 +2697,27 @@ 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
|
@Override
|
||||||
public void resetExpiryTime(Connection txn, ContactId c, MessageId m)
|
public void resetExpiryTime(Connection txn, ContactId c, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -2731,6 +2857,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
affected = ps.executeUpdate();
|
affected = ps.executeUpdate();
|
||||||
if (affected < 0) throw new DbStateException();
|
if (affected < 0) throw new DbStateException();
|
||||||
ps.close();
|
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) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -2738,18 +2883,18 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setReorderingWindow(Connection txn, ContactId c, TransportId t,
|
public void setReorderingWindow(Connection txn, KeySetId k, TransportId t,
|
||||||
long rotationPeriod, long base, byte[] bitmap) throws DbException {
|
long rotationPeriod, long base, byte[] bitmap) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
||||||
+ " WHERE contactId = ? AND transportId = ?"
|
+ " WHERE transportId = ? AND keySetId = ?"
|
||||||
+ " AND rotationPeriod = ?";
|
+ " AND rotationPeriod = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setLong(1, base);
|
ps.setLong(1, base);
|
||||||
ps.setBytes(2, bitmap);
|
ps.setBytes(2, bitmap);
|
||||||
ps.setInt(3, c.getInt());
|
ps.setString(3, t.getString());
|
||||||
ps.setString(4, t.getString());
|
ps.setInt(4, k.getInt());
|
||||||
ps.setLong(5, rotationPeriod);
|
ps.setLong(5, rotationPeriod);
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
@@ -2760,6 +2905,25 @@ 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
|
@Override
|
||||||
public void updateExpiryTime(Connection txn, ContactId c, MessageId m,
|
public void updateExpiryTime(Connection txn, ContactId c, MessageId m,
|
||||||
int maxLatency) throws DbException {
|
int maxLatency) throws DbException {
|
||||||
@@ -2795,45 +2959,69 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateTransportKeys(Connection txn,
|
public void updateTransportKeys(Connection txn, KeySet ks)
|
||||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
// Delete any existing incoming keys
|
// Update the outgoing keys
|
||||||
String sql = "DELETE FROM incomingKeys"
|
String sql = "UPDATE outgoingKeys SET rotationPeriod = ?,"
|
||||||
+ " WHERE contactId = ?"
|
+ " tagKey = ?, headerKey = ?, stream = ?"
|
||||||
+ " AND transportId = ?";
|
+ " WHERE transportId = ? AND keySetId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
TransportKeys k = ks.getTransportKeys();
|
||||||
ps.setInt(1, e.getKey().getInt());
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
ps.setString(2, e.getValue().getTransportId().getString());
|
ps.setLong(1, outCurr.getRotationPeriod());
|
||||||
ps.addBatch();
|
ps.setBytes(2, outCurr.getTagKey().getBytes());
|
||||||
}
|
ps.setBytes(3, outCurr.getHeaderKey().getBytes());
|
||||||
int[] batchAffected = ps.executeBatch();
|
ps.setLong(4, outCurr.getStreamCounter());
|
||||||
if (batchAffected.length != keys.size())
|
ps.setString(5, k.getTransportId().getString());
|
||||||
throw new DbStateException();
|
ps.setInt(6, ks.getKeySetId().getInt());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Delete any existing outgoing keys
|
// Update the incoming keys
|
||||||
sql = "DELETE FROM outgoingKeys"
|
sql = "UPDATE incomingKeys SET rotationPeriod = ?,"
|
||||||
+ " WHERE contactId = ?"
|
+ " tagKey = ?, headerKey = ?, base = ?, bitmap = ?"
|
||||||
+ " AND transportId = ?";
|
+ " WHERE transportId = ? AND keySetId = ?"
|
||||||
|
+ " AND periodOffset = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
ps.setString(6, k.getTransportId().getString());
|
||||||
ps.setInt(1, e.getKey().getInt());
|
ps.setInt(7, ks.getKeySetId().getInt());
|
||||||
ps.setString(2, e.getValue().getTransportId().getString());
|
// Previous rotation period
|
||||||
ps.addBatch();
|
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||||
}
|
ps.setLong(1, inPrev.getRotationPeriod());
|
||||||
batchAffected = ps.executeBatch();
|
ps.setBytes(2, inPrev.getTagKey().getBytes());
|
||||||
if (batchAffected.length != keys.size())
|
ps.setBytes(3, inPrev.getHeaderKey().getBytes());
|
||||||
throw new DbStateException();
|
ps.setLong(4, inPrev.getWindowBase());
|
||||||
|
ps.setBytes(5, inPrev.getWindowBitmap());
|
||||||
|
ps.setInt(8, OFFSET_PREV);
|
||||||
|
ps.addBatch();
|
||||||
|
// Current rotation period
|
||||||
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
|
ps.setLong(1, inCurr.getRotationPeriod());
|
||||||
|
ps.setBytes(2, inCurr.getTagKey().getBytes());
|
||||||
|
ps.setBytes(3, inCurr.getHeaderKey().getBytes());
|
||||||
|
ps.setLong(4, inCurr.getWindowBase());
|
||||||
|
ps.setBytes(5, inCurr.getWindowBitmap());
|
||||||
|
ps.setInt(8, OFFSET_CURR);
|
||||||
|
ps.addBatch();
|
||||||
|
// Next rotation period
|
||||||
|
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||||
|
ps.setLong(1, inNext.getRotationPeriod());
|
||||||
|
ps.setBytes(2, inNext.getTagKey().getBytes());
|
||||||
|
ps.setBytes(3, inNext.getHeaderKey().getBytes());
|
||||||
|
ps.setLong(4, inNext.getWindowBase());
|
||||||
|
ps.setBytes(5, inNext.getWindowBitmap());
|
||||||
|
ps.setInt(8, OFFSET_NEXT);
|
||||||
|
ps.addBatch();
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != 3) throw new DbStateException();
|
||||||
|
for (int rows : batchAffected)
|
||||||
|
if (rows < 0 || rows > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
}
|
}
|
||||||
// Store the new keys
|
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
|
||||||
addTransportKeys(txn, e.getKey(), e.getValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -44,6 +46,8 @@ class KeyAgreementConnector {
|
|||||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
private final ConnectionChooser connectionChooser;
|
private final ConnectionChooser connectionChooser;
|
||||||
|
private final RecordReaderFactory recordReaderFactory;
|
||||||
|
private final RecordWriterFactory recordWriterFactory;
|
||||||
|
|
||||||
private final List<KeyAgreementListener> listeners =
|
private final List<KeyAgreementListener> listeners =
|
||||||
new CopyOnWriteArrayList<>();
|
new CopyOnWriteArrayList<>();
|
||||||
@@ -54,11 +58,15 @@ class KeyAgreementConnector {
|
|||||||
|
|
||||||
KeyAgreementConnector(Callbacks callbacks,
|
KeyAgreementConnector(Callbacks callbacks,
|
||||||
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
|
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
|
||||||
ConnectionChooser connectionChooser) {
|
ConnectionChooser connectionChooser,
|
||||||
|
RecordReaderFactory recordReaderFactory,
|
||||||
|
RecordWriterFactory recordWriterFactory) {
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
this.connectionChooser = connectionChooser;
|
this.connectionChooser = connectionChooser;
|
||||||
|
this.recordReaderFactory = recordReaderFactory;
|
||||||
|
this.recordWriterFactory = recordWriterFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
Payload listen(KeyPair localKeyPair) {
|
Payload listen(KeyPair localKeyPair) {
|
||||||
@@ -119,7 +127,8 @@ class KeyAgreementConnector {
|
|||||||
KeyAgreementConnection chosen =
|
KeyAgreementConnection chosen =
|
||||||
connectionChooser.poll(CONNECTION_TIMEOUT);
|
connectionChooser.poll(CONNECTION_TIMEOUT);
|
||||||
if (chosen == null) return null;
|
if (chosen == null) return null;
|
||||||
return new KeyAgreementTransport(chosen);
|
return new KeyAgreementTransport(recordReaderFactory,
|
||||||
|
recordWriterFactory, chosen);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.info("Interrupted while waiting for connection");
|
LOG.info("Interrupted while waiting for connection");
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent;
|
|||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -48,14 +51,17 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
|||||||
KeyAgreementTaskImpl(CryptoComponent crypto,
|
KeyAgreementTaskImpl(CryptoComponent crypto,
|
||||||
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
|
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
|
||||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||||
ConnectionChooser connectionChooser) {
|
ConnectionChooser connectionChooser,
|
||||||
|
RecordReaderFactory recordReaderFactory,
|
||||||
|
RecordWriterFactory recordWriterFactory) {
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
localKeyPair = crypto.generateAgreementKeyPair();
|
localKeyPair = crypto.generateAgreementKeyPair();
|
||||||
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
|
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
|
||||||
pluginManager, connectionChooser);
|
pluginManager, connectionChooser, recordReaderFactory,
|
||||||
|
recordWriterFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,6 +77,7 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
|||||||
if (localPayload != null) {
|
if (localPayload != null) {
|
||||||
if (remotePayload == null) connector.stopListening();
|
if (remotePayload == null) connector.stopListening();
|
||||||
else interrupt();
|
else interrupt();
|
||||||
|
eventBus.broadcast(new KeyAgreementStoppedListeningEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@@ -14,8 +17,6 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_PAYLOAD_LENGTH_OFFSET;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
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.CONFIRM;
|
||||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
|
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
|
||||||
@@ -30,14 +31,17 @@ class KeyAgreementTransport {
|
|||||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||||
|
|
||||||
private final KeyAgreementConnection kac;
|
private final KeyAgreementConnection kac;
|
||||||
private final InputStream in;
|
private final RecordReader reader;
|
||||||
private final OutputStream out;
|
private final RecordWriter writer;
|
||||||
|
|
||||||
KeyAgreementTransport(KeyAgreementConnection kac)
|
KeyAgreementTransport(RecordReaderFactory recordReaderFactory,
|
||||||
|
RecordWriterFactory recordWriterFactory, KeyAgreementConnection kac)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.kac = kac;
|
this.kac = kac;
|
||||||
in = kac.getConnection().getReader().getInputStream();
|
InputStream in = kac.getConnection().getReader().getInputStream();
|
||||||
out = kac.getConnection().getWriter().getOutputStream();
|
reader = recordReaderFactory.createRecordReader(in);
|
||||||
|
OutputStream out = kac.getConnection().getWriter().getOutputStream();
|
||||||
|
writer = recordWriterFactory.createRecordWriter(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection getConnection() {
|
public DuplexTransportConnection getConnection() {
|
||||||
@@ -74,9 +78,8 @@ class KeyAgreementTransport {
|
|||||||
tryToClose(exception);
|
tryToClose(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tryToClose(boolean exception) {
|
private void tryToClose(boolean exception) {
|
||||||
try {
|
try {
|
||||||
LOG.info("Closing connection");
|
|
||||||
kac.getConnection().getReader().dispose(exception, true);
|
kac.getConnection().getReader().dispose(exception, true);
|
||||||
kac.getConnection().getWriter().dispose(exception);
|
kac.getConnection().getWriter().dispose(exception);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -85,59 +88,27 @@ class KeyAgreementTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeRecord(byte type, byte[] payload) throws IOException {
|
private void writeRecord(byte type, byte[] payload) throws IOException {
|
||||||
byte[] recordHeader = new byte[RECORD_HEADER_LENGTH];
|
writer.writeRecord(new Record(PROTOCOL_VERSION, type, payload));
|
||||||
recordHeader[0] = PROTOCOL_VERSION;
|
writer.flush();
|
||||||
recordHeader[1] = type;
|
|
||||||
ByteUtils.writeUint16(payload.length, recordHeader,
|
|
||||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
|
||||||
out.write(recordHeader);
|
|
||||||
out.write(payload);
|
|
||||||
out.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readRecord(byte expectedType) throws AbortException {
|
private byte[] readRecord(byte expectedType) throws AbortException {
|
||||||
while (true) {
|
while (true) {
|
||||||
byte[] header = readHeader();
|
|
||||||
byte version = header[0], type = header[1];
|
|
||||||
int len = ByteUtils.readUint16(header,
|
|
||||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
|
||||||
// Reject unrecognised protocol version
|
|
||||||
if (version != PROTOCOL_VERSION) throw new AbortException(false);
|
|
||||||
if (type == ABORT) throw new AbortException(true);
|
|
||||||
if (type == expectedType) {
|
|
||||||
try {
|
|
||||||
return readData(len);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AbortException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Reject recognised but unexpected record type
|
|
||||||
if (type == KEY || type == CONFIRM) throw new AbortException(false);
|
|
||||||
// Skip unrecognised record type
|
|
||||||
try {
|
try {
|
||||||
readData(len);
|
Record record = reader.readRecord();
|
||||||
|
// Reject unrecognised protocol version
|
||||||
|
if (record.getProtocolVersion() != PROTOCOL_VERSION)
|
||||||
|
throw new AbortException(false);
|
||||||
|
byte type = record.getRecordType();
|
||||||
|
if (type == ABORT) throw new AbortException(true);
|
||||||
|
if (type == expectedType) return record.getPayload();
|
||||||
|
// Reject recognised but unexpected record type
|
||||||
|
if (type == KEY || type == CONFIRM)
|
||||||
|
throw new AbortException(false);
|
||||||
|
// Skip unrecognised record type
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new AbortException(e);
|
throw new AbortException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readHeader() throws AbortException {
|
|
||||||
try {
|
|
||||||
return readData(RECORD_HEADER_LENGTH);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AbortException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readData(int len) throws IOException {
|
|
||||||
byte[] data = new byte[len];
|
|
||||||
int offset = 0;
|
|
||||||
while (offset < data.length) {
|
|
||||||
int read = in.read(data, offset, data.length - offset);
|
|
||||||
if (read == -1) throw new EOFException();
|
|
||||||
offset += read;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.briarproject.bramble.plugin.bluetooth;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
interface BluetoothConnectionLimiter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the limiter that key agreement has started.
|
||||||
|
*/
|
||||||
|
void keyAgreementStarted();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the limiter that key agreement has ended.
|
||||||
|
*/
|
||||||
|
void keyAgreementEnded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a contact connection can be opened. This method does not
|
||||||
|
* need to be called for key agreement connections.
|
||||||
|
*/
|
||||||
|
boolean canOpenContactConnection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the limiter that a contact connection has been opened. The
|
||||||
|
* limiter may close the new connection if key agreement is in progress.
|
||||||
|
* <p/>
|
||||||
|
* Returns false if the limiter has closed the new connection.
|
||||||
|
*/
|
||||||
|
boolean contactConnectionOpened(DuplexTransportConnection conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the limiter that a key agreement connection has been opened.
|
||||||
|
*/
|
||||||
|
void keyAgreementConnectionOpened(DuplexTransportConnection conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the limiter that the given connection has been closed.
|
||||||
|
*/
|
||||||
|
void connectionClosed(DuplexTransportConnection conn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the limiter that all connections have been closed.
|
||||||
|
*/
|
||||||
|
void allConnectionsClosed();
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
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.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
@ThreadSafe
|
||||||
|
class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(BluetoothConnectionLimiterImpl.class.getName());
|
||||||
|
|
||||||
|
private final Object lock = new Object();
|
||||||
|
// The following are locking: lock
|
||||||
|
private final LinkedList<DuplexTransportConnection> connections =
|
||||||
|
new LinkedList<>();
|
||||||
|
private boolean keyAgreementInProgress = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyAgreementStarted() {
|
||||||
|
List<DuplexTransportConnection> close;
|
||||||
|
synchronized (lock) {
|
||||||
|
keyAgreementInProgress = true;
|
||||||
|
close = new ArrayList<>(connections);
|
||||||
|
connections.clear();
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Key agreement started, closing " + close.size() +
|
||||||
|
" connections");
|
||||||
|
}
|
||||||
|
for (DuplexTransportConnection conn : close) tryToClose(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyAgreementEnded() {
|
||||||
|
synchronized (lock) {
|
||||||
|
keyAgreementInProgress = false;
|
||||||
|
}
|
||||||
|
LOG.info("Key agreement ended");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canOpenContactConnection() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (keyAgreementInProgress) {
|
||||||
|
LOG.info("Can't open contact connection during key agreement");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
LOG.info("Can open contact connection");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contactConnectionOpened(DuplexTransportConnection conn) {
|
||||||
|
boolean accept = true;
|
||||||
|
synchronized (lock) {
|
||||||
|
if (keyAgreementInProgress) {
|
||||||
|
LOG.info("Refusing contact connection during key agreement");
|
||||||
|
accept = false;
|
||||||
|
} else {
|
||||||
|
LOG.info("Accepting contact connection");
|
||||||
|
connections.add(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!accept) tryToClose(conn);
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
|
||||||
|
synchronized (lock) {
|
||||||
|
LOG.info("Accepting key agreement connection");
|
||||||
|
connections.add(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(DuplexTransportConnection conn) {
|
||||||
|
try {
|
||||||
|
conn.getWriter().dispose(false);
|
||||||
|
conn.getReader().dispose(false, false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connectionClosed(DuplexTransportConnection conn) {
|
||||||
|
synchronized (lock) {
|
||||||
|
connections.remove(conn);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connection closed, " + connections.size() + " open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void allConnectionsClosed() {
|
||||||
|
synchronized (lock) {
|
||||||
|
connections.clear();
|
||||||
|
LOG.info("All connections closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.event.Event;
|
|||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStoppedListeningEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
@@ -51,6 +53,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||||
|
|
||||||
|
final BluetoothConnectionLimiter connectionLimiter;
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final Backoff backoff;
|
private final Backoff backoff;
|
||||||
@@ -91,8 +95,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||||
throws IOException;
|
throws IOException;
|
||||||
|
|
||||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||||
|
Executor ioExecutor, SecureRandom secureRandom,
|
||||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||||
|
this.connectionLimiter = connectionLimiter;
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
this.backoff = backoff;
|
this.backoff = backoff;
|
||||||
@@ -110,6 +116,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
void onAdapterDisabled() {
|
void onAdapterDisabled() {
|
||||||
LOG.info("Bluetooth disabled");
|
LOG.info("Bluetooth disabled");
|
||||||
tryToClose(socket);
|
tryToClose(socket);
|
||||||
|
connectionLimiter.allConnectionsClosed();
|
||||||
callback.transportDisabled();
|
callback.transportDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +220,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
callback.incomingConnectionCreated(conn);
|
if (connectionLimiter.contactConnectionOpened(conn))
|
||||||
|
callback.incomingConnectionCreated(conn);
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,10 +265,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||||
|
if (!connectionLimiter.canOpenContactConnection()) return;
|
||||||
DuplexTransportConnection conn = connect(address, uuid);
|
DuplexTransportConnection conn = connect(address, uuid);
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
backoff.reset();
|
backoff.reset();
|
||||||
callback.outgoingConnectionCreated(c, conn);
|
if (connectionLimiter.contactConnectionOpened(conn))
|
||||||
|
callback.outgoingConnectionCreated(c, conn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -300,12 +310,16 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createConnection(ContactId c) {
|
public DuplexTransportConnection createConnection(ContactId c) {
|
||||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||||
|
if (!connectionLimiter.canOpenContactConnection()) return null;
|
||||||
TransportProperties p = callback.getRemoteProperties(c);
|
TransportProperties p = callback.getRemoteProperties(c);
|
||||||
String address = p.get(PROP_ADDRESS);
|
String address = p.get(PROP_ADDRESS);
|
||||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||||
String uuid = p.get(PROP_UUID);
|
String uuid = p.get(PROP_UUID);
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
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 connectionLimiter.contactConnectionOpened(conn) ? conn : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -355,7 +369,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||||
return connect(address, uuid);
|
DuplexTransportConnection conn = connect(address, uuid);
|
||||||
|
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||||
@@ -376,6 +392,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||||
if (s.getNamespace().equals(ID.getString()))
|
if (s.getNamespace().equals(ID.getString()))
|
||||||
ioExecutor.execute(this::onSettingsUpdated);
|
ioExecutor.execute(this::onSettingsUpdated);
|
||||||
|
} else if (e instanceof KeyAgreementListeningEvent) {
|
||||||
|
ioExecutor.execute(connectionLimiter::keyAgreementStarted);
|
||||||
|
} else if (e instanceof KeyAgreementStoppedListeningEvent) {
|
||||||
|
ioExecutor.execute(connectionLimiter::keyAgreementEnded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,6 +428,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
public KeyAgreementConnection accept() throws IOException {
|
public KeyAgreementConnection accept() throws IOException {
|
||||||
DuplexTransportConnection conn = acceptConnection(ss);
|
DuplexTransportConnection conn = acceptConnection(ss);
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||||
|
connectionLimiter.keyAgreementConnectionOpened(conn);
|
||||||
return new KeyAgreementConnection(conn, ID);
|
return new KeyAgreementConnection(conn, ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -241,10 +241,11 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Socket s = new Socket();
|
|
||||||
try {
|
try {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||||
|
Socket s = createSocket();
|
||||||
|
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
||||||
s.connect(remote);
|
s.connect(remote);
|
||||||
s.setSoTimeout(socketTimeout);
|
s.setSoTimeout(socketTimeout);
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin.tcp;
|
package org.briarproject.bramble.plugin.tcp;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.PoliteExecutor;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
@@ -47,7 +48,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(TcpPlugin.class.getName());
|
Logger.getLogger(TcpPlugin.class.getName());
|
||||||
|
|
||||||
protected final Executor ioExecutor;
|
protected final Executor ioExecutor, bindExecutor;
|
||||||
protected final Backoff backoff;
|
protected final Backoff backoff;
|
||||||
protected final DuplexPluginCallback callback;
|
protected final DuplexPluginCallback callback;
|
||||||
protected final int maxLatency, maxIdleTime, socketTimeout;
|
protected final int maxLatency, maxIdleTime, socketTimeout;
|
||||||
@@ -90,6 +91,8 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
||||||
socketTimeout = Integer.MAX_VALUE;
|
socketTimeout = Integer.MAX_VALUE;
|
||||||
else socketTimeout = maxIdleTime * 2;
|
else socketTimeout = maxIdleTime * 2;
|
||||||
|
// Don't execute more than one bind operation at a time
|
||||||
|
bindExecutor = new PoliteExecutor("TcpPlugin", ioExecutor, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -110,8 +113,9 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void bind() {
|
protected void bind() {
|
||||||
ioExecutor.execute(() -> {
|
bindExecutor.execute(() -> {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
|
if (socket != null && !socket.isClosed()) return;
|
||||||
ServerSocket ss = null;
|
ServerSocket ss = null;
|
||||||
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
||||||
try {
|
try {
|
||||||
@@ -243,10 +247,11 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Socket s = new Socket();
|
|
||||||
try {
|
try {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||||
|
Socket s = createSocket();
|
||||||
|
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
||||||
s.connect(remote);
|
s.connect(remote);
|
||||||
s.setSoTimeout(socketTimeout);
|
s.setSoTimeout(socketTimeout);
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
@@ -261,6 +266,10 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Socket createSocket() throws IOException {
|
||||||
|
return new Socket();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
InetSocketAddress parseSocketAddress(String ipPort) {
|
InetSocketAddress parseSocketAddress(String ipPort) {
|
||||||
if (StringUtils.isNullOrEmpty(ipPort)) return null;
|
if (StringUtils.isNullOrEmpty(ipPort)) return null;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -15,6 +16,8 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
||||||
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MAJOR_VERSION;
|
||||||
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.MINOR_VERSION;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class PropertiesModule {
|
public class PropertiesModule {
|
||||||
@@ -33,7 +36,8 @@ public class PropertiesModule {
|
|||||||
Clock clock) {
|
Clock clock) {
|
||||||
TransportPropertyValidator validator = new TransportPropertyValidator(
|
TransportPropertyValidator validator = new TransportPropertyValidator(
|
||||||
clientHelper, metadataEncoder, clock);
|
clientHelper, metadataEncoder, clock);
|
||||||
validationManager.registerMessageValidator(CLIENT_ID, validator);
|
validationManager.registerMessageValidator(CLIENT_ID, MAJOR_VERSION,
|
||||||
|
validator);
|
||||||
return validator;
|
return validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +46,14 @@ public class PropertiesModule {
|
|||||||
TransportPropertyManager getTransportPropertyManager(
|
TransportPropertyManager getTransportPropertyManager(
|
||||||
LifecycleManager lifecycleManager,
|
LifecycleManager lifecycleManager,
|
||||||
ValidationManager validationManager, ContactManager contactManager,
|
ValidationManager validationManager, ContactManager contactManager,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
TransportPropertyManagerImpl transportPropertyManager) {
|
TransportPropertyManagerImpl transportPropertyManager) {
|
||||||
lifecycleManager.registerClient(transportPropertyManager);
|
lifecycleManager.registerClient(transportPropertyManager);
|
||||||
validationManager.registerIncomingMessageHook(CLIENT_ID,
|
validationManager.registerIncomingMessageHook(CLIENT_ID, MAJOR_VERSION,
|
||||||
transportPropertyManager);
|
transportPropertyManager);
|
||||||
contactManager.registerAddContactHook(transportPropertyManager);
|
contactManager.registerContactHook(transportPropertyManager);
|
||||||
contactManager.registerRemoveContactHook(transportPropertyManager);
|
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||||
|
MINOR_VERSION, transportPropertyManager);
|
||||||
return transportPropertyManager;
|
return transportPropertyManager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import org.briarproject.bramble.api.client.ClientHelper;
|
|||||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager.AddContactHook;
|
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
|
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
import org.briarproject.bramble.api.data.MetadataParser;
|
||||||
@@ -20,12 +19,15 @@ import org.briarproject.bramble.api.properties.TransportProperties;
|
|||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.bramble.api.sync.Client;
|
import org.briarproject.bramble.api.sync.Client;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -35,15 +37,14 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class TransportPropertyManagerImpl implements TransportPropertyManager,
|
class TransportPropertyManagerImpl implements TransportPropertyManager,
|
||||||
Client, AddContactHook, RemoveContactHook, IncomingMessageHook {
|
Client, ContactHook, ClientVersioningHook, IncomingMessageHook {
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
|
private final ClientVersioningManager clientVersioningManager;
|
||||||
private final MetadataParser metadataParser;
|
private final MetadataParser metadataParser;
|
||||||
private final ContactGroupFactory contactGroupFactory;
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
@@ -51,22 +52,25 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
TransportPropertyManagerImpl(DatabaseComponent db,
|
TransportPropertyManagerImpl(DatabaseComponent db,
|
||||||
ClientHelper clientHelper, MetadataParser metadataParser,
|
ClientHelper clientHelper,
|
||||||
|
ClientVersioningManager clientVersioningManager,
|
||||||
|
MetadataParser metadataParser,
|
||||||
ContactGroupFactory contactGroupFactory, Clock clock) {
|
ContactGroupFactory contactGroupFactory, Clock clock) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
|
this.clientVersioningManager = clientVersioningManager;
|
||||||
this.metadataParser = metadataParser;
|
this.metadataParser = metadataParser;
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||||
CLIENT_VERSION);
|
MAJOR_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createLocalState(Transaction txn) throws DbException {
|
public void createLocalState(Transaction txn) throws DbException {
|
||||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||||
db.addGroup(txn, localGroup);
|
db.addGroup(txn, localGroup);
|
||||||
// Ensure we've set things up for any pre-existing contacts
|
// Set things up for any pre-existing contacts
|
||||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +78,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
public void addingContact(Transaction txn, Contact c) throws DbException {
|
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||||
// Create a group to share with the contact
|
// Create a group to share with the contact
|
||||||
Group g = getContactGroup(c);
|
Group g = getContactGroup(c);
|
||||||
// Return if we've already set things up for this contact
|
|
||||||
if (db.containsGroup(txn, g.getId())) return;
|
|
||||||
// Store the group and share it with the contact
|
|
||||||
db.addGroup(txn, g);
|
db.addGroup(txn, g);
|
||||||
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
// Apply the client's visibility to the contact group
|
||||||
|
Visibility client = clientVersioningManager.getClientVisibility(txn,
|
||||||
|
c.getId(), CLIENT_ID, MAJOR_VERSION);
|
||||||
|
db.setGroupVisibility(txn, c.getId(), g.getId(), client);
|
||||||
// Copy the latest local properties into the group
|
// Copy the latest local properties into the group
|
||||||
Map<TransportId, TransportProperties> local = getLocalProperties(txn);
|
Map<TransportId, TransportProperties> local = getLocalProperties(txn);
|
||||||
for (Entry<TransportId, TransportProperties> e : local.entrySet()) {
|
for (Entry<TransportId, TransportProperties> e : local.entrySet()) {
|
||||||
@@ -92,6 +96,14 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
db.removeGroup(txn, getContactGroup(c));
|
db.removeGroup(txn, getContactGroup(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClientVisibilityChanging(Transaction txn, Contact c,
|
||||||
|
Visibility v) throws DbException {
|
||||||
|
// Apply the client's visibility to the contact group
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
db.setGroupVisibility(txn, c.getId(), g.getId(), v);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
throws DbException, InvalidMessageException {
|
throws DbException, InvalidMessageException {
|
||||||
@@ -290,7 +302,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
|
|
||||||
private Group getContactGroup(Contact c) {
|
private Group getContactGroup(Contact c) {
|
||||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||||
CLIENT_VERSION, c);
|
MAJOR_VERSION, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
||||||
@@ -348,10 +360,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
throws FormatException {
|
throws FormatException {
|
||||||
// Transport ID, version, properties
|
// Transport ID, version, properties
|
||||||
BdfDictionary dictionary = message.getDictionary(2);
|
BdfDictionary dictionary = message.getDictionary(2);
|
||||||
TransportProperties p = new TransportProperties();
|
return clientHelper.parseAndValidateTransportProperties(dictionary);
|
||||||
for (String key : dictionary.keySet())
|
|
||||||
p.put(key, dictionary.getString(key));
|
|
||||||
return p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LatestUpdate {
|
private static class LatestUpdate {
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import org.briarproject.bramble.api.system.Clock;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
|
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.checkLength;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
|
||||||
@@ -42,12 +40,7 @@ class TransportPropertyValidator extends BdfMessageValidator {
|
|||||||
if (version < 0) throw new FormatException();
|
if (version < 0) throw new FormatException();
|
||||||
// Properties
|
// Properties
|
||||||
BdfDictionary dictionary = body.getDictionary(2);
|
BdfDictionary dictionary = body.getDictionary(2);
|
||||||
checkSize(dictionary, 0, MAX_PROPERTIES_PER_TRANSPORT);
|
clientHelper.parseAndValidateTransportProperties(dictionary);
|
||||||
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
|
// Return the metadata
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put("transportId", transportId);
|
meta.put("transportId", transportId);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.briarproject.bramble.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class RecordModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
RecordReaderFactory provideRecordReaderFactory() {
|
||||||
|
return new RecordReaderFactoryImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
RecordWriterFactory provideRecordWriterFactory() {
|
||||||
|
return new RecordWriterFactoryImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.briarproject.bramble.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
class RecordReaderFactoryImpl implements RecordReaderFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecordReader createRecordReader(InputStream in) {
|
||||||
|
return new RecordReaderImpl(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.briarproject.bramble.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
|
|
||||||
|
@NotThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class RecordReaderImpl implements RecordReader {
|
||||||
|
|
||||||
|
private final DataInputStream in;
|
||||||
|
private final byte[] header = new byte[RECORD_HEADER_BYTES];
|
||||||
|
|
||||||
|
RecordReaderImpl(InputStream in) {
|
||||||
|
this.in = new DataInputStream(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Record readRecord() throws IOException {
|
||||||
|
in.readFully(header);
|
||||||
|
byte protocolVersion = header[0];
|
||||||
|
byte recordType = header[1];
|
||||||
|
int payloadLength = ByteUtils.readUint16(header, 2);
|
||||||
|
if (payloadLength < 0 || payloadLength > MAX_RECORD_PAYLOAD_BYTES)
|
||||||
|
throw new FormatException();
|
||||||
|
byte[] payload = new byte[payloadLength];
|
||||||
|
in.readFully(payload);
|
||||||
|
return new Record(protocolVersion, recordType, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.briarproject.bramble.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
class RecordWriterFactoryImpl implements RecordWriterFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecordWriter createRecordWriter(OutputStream out) {
|
||||||
|
return new RecordWriterImpl(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.briarproject.bramble.record;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
|
||||||
|
|
||||||
|
@NotThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class RecordWriterImpl implements RecordWriter {
|
||||||
|
|
||||||
|
private final OutputStream out;
|
||||||
|
private final byte[] header = new byte[RECORD_HEADER_BYTES];
|
||||||
|
|
||||||
|
RecordWriterImpl(OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeRecord(Record r) throws IOException {
|
||||||
|
byte[] payload = r.getPayload();
|
||||||
|
header[0] = r.getProtocolVersion();
|
||||||
|
header[1] = r.getRecordType();
|
||||||
|
ByteUtils.writeUint16(payload.length, header, 2);
|
||||||
|
out.write(header);
|
||||||
|
out.write(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,8 @@ import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
|
||||||
@@ -39,8 +39,8 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An outgoing {@link SyncSession} suitable for duplex transports. The session
|
* An outgoing {@link SyncSession} suitable for duplex transports. The session
|
||||||
@@ -67,7 +67,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final int maxLatency, maxIdleTime;
|
private final int maxLatency, maxIdleTime;
|
||||||
private final RecordWriter recordWriter;
|
private final SyncRecordWriter recordWriter;
|
||||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||||
|
|
||||||
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
||||||
@@ -81,7 +81,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
|
|
||||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
|
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
|
||||||
int maxIdleTime, RecordWriter recordWriter) {
|
int maxIdleTime, SyncRecordWriter recordWriter) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
@@ -273,7 +273,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
b = db.generateRequestedBatch(txn, contactId,
|
b = db.generateRequestedBatch(txn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
MAX_RECORD_PAYLOAD_BYTES, maxLatency);
|
||||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -12,14 +12,17 @@ import org.briarproject.bramble.util.StringUtils;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
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.GroupId.LABEL;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class GroupFactoryImpl implements GroupFactory {
|
class GroupFactoryImpl implements GroupFactory {
|
||||||
|
|
||||||
|
private static final byte[] FORMAT_VERSION_BYTES =
|
||||||
|
new byte[] {FORMAT_VERSION};
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -28,12 +31,12 @@ class GroupFactoryImpl implements GroupFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
public Group createGroup(ClientId c, int majorVersion, byte[] descriptor) {
|
||||||
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
byte[] majorVersionBytes = new byte[INT_32_BYTES];
|
||||||
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
ByteUtils.writeUint32(majorVersion, majorVersionBytes, 0);
|
||||||
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
byte[] hash = crypto.hash(LABEL, FORMAT_VERSION_BYTES,
|
||||||
StringUtils.toUtf8(c.getString()), clientVersionBytes,
|
StringUtils.toUtf8(c.getString()), majorVersionBytes,
|
||||||
descriptor);
|
descriptor);
|
||||||
return new Group(new GroupId(hash), c, descriptor);
|
return new Group(new GroupId(hash), c, majorVersion, descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.RecordReader;
|
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -43,13 +43,13 @@ class IncomingSession implements SyncSession, EventListener {
|
|||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final RecordReader recordReader;
|
private final SyncRecordReader recordReader;
|
||||||
|
|
||||||
private volatile boolean interrupted = false;
|
private volatile boolean interrupted = false;
|
||||||
|
|
||||||
IncomingSession(DatabaseComponent db, Executor dbExecutor,
|
IncomingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
EventBus eventBus, ContactId contactId,
|
EventBus eventBus, ContactId contactId,
|
||||||
RecordReader recordReader) {
|
SyncRecordReader recordReader) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
|||||||
@@ -12,15 +12,21 @@ import org.briarproject.bramble.util.ByteUtils;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.MessageId.LABEL;
|
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.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class MessageFactoryImpl implements MessageFactory {
|
class MessageFactoryImpl implements MessageFactory {
|
||||||
|
|
||||||
|
private static final byte[] FORMAT_VERSION_BYTES =
|
||||||
|
new byte[] {FORMAT_VERSION};
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -32,11 +38,7 @@ class MessageFactoryImpl implements MessageFactory {
|
|||||||
public Message createMessage(GroupId g, long timestamp, byte[] body) {
|
public Message createMessage(GroupId g, long timestamp, byte[] body) {
|
||||||
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
MessageId id = getMessageId(g, timestamp, body);
|
||||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
|
||||||
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];
|
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
||||||
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||||
@@ -44,10 +46,38 @@ class MessageFactoryImpl implements MessageFactory {
|
|||||||
return new Message(id, g, timestamp, raw);
|
return new Message(id, g, timestamp, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MessageId getMessageId(GroupId g, long timestamp, byte[] body) {
|
||||||
|
// There's only one block, so the root hash is the hash of the block
|
||||||
|
byte[] rootHash = crypto.hash(BLOCK_LABEL, FORMAT_VERSION_BYTES, body);
|
||||||
|
byte[] timeBytes = new byte[INT_64_BYTES];
|
||||||
|
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||||
|
byte[] idHash = crypto.hash(ID_LABEL, FORMAT_VERSION_BYTES,
|
||||||
|
g.getBytes(), timeBytes, rootHash);
|
||||||
|
return new MessageId(idHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message createMessage(byte[] raw) {
|
||||||
|
if (raw.length < MESSAGE_HEADER_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (raw.length > MAX_MESSAGE_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
byte[] groupId = new byte[UniqueId.LENGTH];
|
||||||
|
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
|
||||||
|
GroupId g = new GroupId(groupId);
|
||||||
|
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
||||||
|
byte[] body = new byte[raw.length - MESSAGE_HEADER_LENGTH];
|
||||||
|
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
|
||||||
|
MessageId id = getMessageId(g, timestamp, body);
|
||||||
|
return new Message(id, g, timestamp, raw);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message createMessage(MessageId m, byte[] raw) {
|
public Message createMessage(MessageId m, byte[] raw) {
|
||||||
if (raw.length < MESSAGE_HEADER_LENGTH)
|
if (raw.length < MESSAGE_HEADER_LENGTH)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
if (raw.length > MAX_MESSAGE_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
byte[] groupId = new byte[UniqueId.LENGTH];
|
byte[] groupId = new byte[UniqueId.LENGTH];
|
||||||
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
|
System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
|
||||||
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package org.briarproject.bramble.sync;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
|
||||||
import org.briarproject.bramble.api.sync.RecordReader;
|
|
||||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class RecordReaderFactoryImpl implements RecordReaderFactory {
|
|
||||||
|
|
||||||
private final MessageFactory messageFactory;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
RecordReaderFactoryImpl(MessageFactory messageFactory) {
|
|
||||||
this.messageFactory = messageFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RecordReader createRecordReader(InputStream in) {
|
|
||||||
return new RecordReaderImpl(messageFactory, in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.briarproject.bramble.sync;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
|
||||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class RecordWriterFactoryImpl implements RecordWriterFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RecordWriter createRecordWriter(OutputStream out) {
|
|
||||||
return new RecordWriterImpl(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -29,8 +29,8 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An outgoing {@link SyncSession} suitable for simplex transports. The session
|
* An outgoing {@link SyncSession} suitable for simplex transports. The session
|
||||||
@@ -51,7 +51,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final int maxLatency;
|
private final int maxLatency;
|
||||||
private final RecordWriter recordWriter;
|
private final SyncRecordWriter recordWriter;
|
||||||
private final AtomicInteger outstandingQueries;
|
private final AtomicInteger outstandingQueries;
|
||||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
|
|
||||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
EventBus eventBus, ContactId contactId,
|
EventBus eventBus, ContactId contactId,
|
||||||
int maxLatency, RecordWriter recordWriter) {
|
int maxLatency, SyncRecordWriter recordWriter) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
@@ -171,7 +171,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
b = db.generateBatch(txn, contactId,
|
b = db.generateBatch(txn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
MAX_RECORD_PAYLOAD_BYTES, maxLatency);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import org.briarproject.bramble.api.event.EventBus;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.sync.GroupFactory;
|
import org.briarproject.bramble.api.sync.GroupFactory;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
|
||||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
@@ -52,22 +52,23 @@ public class SyncModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
RecordReaderFactory provideRecordReaderFactory(
|
SyncRecordReaderFactory provideRecordReaderFactory(
|
||||||
RecordReaderFactoryImpl recordReaderFactory) {
|
SyncRecordReaderFactoryImpl recordReaderFactory) {
|
||||||
return recordReaderFactory;
|
return recordReaderFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
RecordWriterFactory provideRecordWriterFactory() {
|
SyncRecordWriterFactory provideRecordWriterFactory(
|
||||||
return new RecordWriterFactoryImpl();
|
SyncRecordWriterFactoryImpl recordWriterFactory) {
|
||||||
|
return recordWriterFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db,
|
SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db,
|
||||||
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
||||||
Clock clock, RecordReaderFactory recordReaderFactory,
|
Clock clock, SyncRecordReaderFactory recordReaderFactory,
|
||||||
RecordWriterFactory recordWriterFactory) {
|
SyncRecordWriterFactory recordWriterFactory) {
|
||||||
return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
|
return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
|
||||||
recordReaderFactory, recordWriterFactory);
|
recordReaderFactory, recordWriterFactory);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class SyncRecordReaderFactoryImpl implements SyncRecordReaderFactory {
|
||||||
|
|
||||||
|
private final MessageFactory messageFactory;
|
||||||
|
private final RecordReaderFactory recordReaderFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SyncRecordReaderFactoryImpl(MessageFactory messageFactory,
|
||||||
|
RecordReaderFactory recordReaderFactory) {
|
||||||
|
this.messageFactory = messageFactory;
|
||||||
|
this.recordReaderFactory = recordReaderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncRecordReader createRecordReader(InputStream in) {
|
||||||
|
RecordReader reader = recordReaderFactory.createRecordReader(in);
|
||||||
|
return new SyncRecordReaderImpl(messageFactory, reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,82 +3,56 @@ package org.briarproject.bramble.sync;
|
|||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordReader;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.RecordReader;
|
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.NotThreadSafe;
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
|
||||||
|
|
||||||
@NotThreadSafe
|
@NotThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class RecordReaderImpl implements RecordReader {
|
class SyncRecordReaderImpl implements SyncRecordReader {
|
||||||
|
|
||||||
private enum State {BUFFER_EMPTY, BUFFER_FULL, EOF}
|
|
||||||
|
|
||||||
private final MessageFactory messageFactory;
|
private final MessageFactory messageFactory;
|
||||||
private final InputStream in;
|
private final RecordReader reader;
|
||||||
private final byte[] header, payload;
|
|
||||||
|
|
||||||
private State state = State.BUFFER_EMPTY;
|
@Nullable
|
||||||
private int payloadLength = 0;
|
private Record nextRecord = null;
|
||||||
|
private boolean eof = false;
|
||||||
|
|
||||||
RecordReaderImpl(MessageFactory messageFactory, InputStream in) {
|
SyncRecordReaderImpl(MessageFactory messageFactory, RecordReader reader) {
|
||||||
this.messageFactory = messageFactory;
|
this.messageFactory = messageFactory;
|
||||||
this.in = in;
|
this.reader = reader;
|
||||||
header = new byte[RECORD_HEADER_LENGTH];
|
|
||||||
payload = new byte[MAX_RECORD_PAYLOAD_LENGTH];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readRecord() throws IOException {
|
private void readRecord() throws IOException {
|
||||||
if (state != State.BUFFER_EMPTY) throw new IllegalStateException();
|
assert nextRecord == null;
|
||||||
while (true) {
|
while (true) {
|
||||||
// Read the header
|
nextRecord = reader.readRecord();
|
||||||
int offset = 0;
|
|
||||||
while (offset < RECORD_HEADER_LENGTH) {
|
|
||||||
int read =
|
|
||||||
in.read(header, offset, RECORD_HEADER_LENGTH - offset);
|
|
||||||
if (read == -1) {
|
|
||||||
if (offset > 0) throw new FormatException();
|
|
||||||
state = State.EOF;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
offset += read;
|
|
||||||
}
|
|
||||||
byte version = header[0], type = header[1];
|
|
||||||
payloadLength = ByteUtils.readUint16(header, 2);
|
|
||||||
// Check the protocol version
|
// Check the protocol version
|
||||||
|
byte version = nextRecord.getProtocolVersion();
|
||||||
if (version != PROTOCOL_VERSION) throw new FormatException();
|
if (version != PROTOCOL_VERSION) throw new FormatException();
|
||||||
// Check the payload length
|
byte type = nextRecord.getRecordType();
|
||||||
if (payloadLength > MAX_RECORD_PAYLOAD_LENGTH)
|
|
||||||
throw new FormatException();
|
|
||||||
// Read the payload
|
|
||||||
offset = 0;
|
|
||||||
while (offset < payloadLength) {
|
|
||||||
int read = in.read(payload, offset, payloadLength - offset);
|
|
||||||
if (read == -1) throw new FormatException();
|
|
||||||
offset += read;
|
|
||||||
}
|
|
||||||
state = State.BUFFER_FULL;
|
|
||||||
// Return if this is a known record type, otherwise continue
|
// Return if this is a known record type, otherwise continue
|
||||||
if (type == ACK || type == MESSAGE || type == OFFER ||
|
if (type == ACK || type == MESSAGE || type == OFFER ||
|
||||||
type == REQUEST) {
|
type == REQUEST) {
|
||||||
@@ -87,6 +61,11 @@ class RecordReaderImpl implements RecordReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte getNextRecordType() {
|
||||||
|
assert nextRecord != null;
|
||||||
|
return nextRecord.getRecordType();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if there's another record available or false if we've
|
* Returns true if there's another record available or false if we've
|
||||||
* reached the end of the input stream.
|
* reached the end of the input stream.
|
||||||
@@ -97,14 +76,21 @@ class RecordReaderImpl implements RecordReader {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean eof() throws IOException {
|
public boolean eof() throws IOException {
|
||||||
if (state == State.BUFFER_EMPTY) readRecord();
|
if (nextRecord != null) return false;
|
||||||
if (state == State.BUFFER_EMPTY) throw new IllegalStateException();
|
if (eof) return true;
|
||||||
return state == State.EOF;
|
try {
|
||||||
|
readRecord();
|
||||||
|
return false;
|
||||||
|
} catch (EOFException e) {
|
||||||
|
nextRecord = null;
|
||||||
|
eof = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasAck() throws IOException {
|
public boolean hasAck() throws IOException {
|
||||||
return !eof() && header[1] == ACK;
|
return !eof() && getNextRecordType() == ACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -114,45 +100,41 @@ class RecordReaderImpl implements RecordReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<MessageId> readMessageIds() throws IOException {
|
private List<MessageId> readMessageIds() throws IOException {
|
||||||
if (payloadLength == 0) throw new FormatException();
|
assert nextRecord != null;
|
||||||
if (payloadLength % UniqueId.LENGTH != 0) throw new FormatException();
|
byte[] payload = nextRecord.getPayload();
|
||||||
List<MessageId> ids = new ArrayList<>();
|
if (payload.length == 0) throw new FormatException();
|
||||||
for (int off = 0; off < payloadLength; off += UniqueId.LENGTH) {
|
if (payload.length % UniqueId.LENGTH != 0) throw new FormatException();
|
||||||
|
List<MessageId> ids = new ArrayList<>(payload.length / UniqueId.LENGTH);
|
||||||
|
for (int off = 0; off < payload.length; off += UniqueId.LENGTH) {
|
||||||
byte[] id = new byte[UniqueId.LENGTH];
|
byte[] id = new byte[UniqueId.LENGTH];
|
||||||
System.arraycopy(payload, off, id, 0, UniqueId.LENGTH);
|
System.arraycopy(payload, off, id, 0, UniqueId.LENGTH);
|
||||||
ids.add(new MessageId(id));
|
ids.add(new MessageId(id));
|
||||||
}
|
}
|
||||||
state = State.BUFFER_EMPTY;
|
nextRecord = null;
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasMessage() throws IOException {
|
public boolean hasMessage() throws IOException {
|
||||||
return !eof() && header[1] == MESSAGE;
|
return !eof() && getNextRecordType() == MESSAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Message readMessage() throws IOException {
|
public Message readMessage() throws IOException {
|
||||||
if (!hasMessage()) throw new FormatException();
|
if (!hasMessage()) throw new FormatException();
|
||||||
if (payloadLength <= MESSAGE_HEADER_LENGTH) throw new FormatException();
|
assert nextRecord != null;
|
||||||
// Group ID
|
byte[] payload = nextRecord.getPayload();
|
||||||
byte[] id = new byte[UniqueId.LENGTH];
|
if (payload.length < MESSAGE_HEADER_LENGTH) throw new FormatException();
|
||||||
System.arraycopy(payload, 0, id, 0, UniqueId.LENGTH);
|
// Validate timestamp
|
||||||
GroupId groupId = new GroupId(id);
|
|
||||||
// Timestamp
|
|
||||||
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
|
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
|
||||||
if (timestamp < 0) throw new FormatException();
|
if (timestamp < 0) throw new FormatException();
|
||||||
// Body
|
nextRecord = null;
|
||||||
byte[] body = new byte[payloadLength - MESSAGE_HEADER_LENGTH];
|
return messageFactory.createMessage(payload);
|
||||||
System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
|
|
||||||
payloadLength - MESSAGE_HEADER_LENGTH);
|
|
||||||
state = State.BUFFER_EMPTY;
|
|
||||||
return messageFactory.createMessage(groupId, timestamp, body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasOffer() throws IOException {
|
public boolean hasOffer() throws IOException {
|
||||||
return !eof() && header[1] == OFFER;
|
return !eof() && getNextRecordType() == OFFER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -163,7 +145,7 @@ class RecordReaderImpl implements RecordReader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasRequest() throws IOException {
|
public boolean hasRequest() throws IOException {
|
||||||
return !eof() && header[1] == REQUEST;
|
return !eof() && getNextRecordType() == REQUEST;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class SyncRecordWriterFactoryImpl implements SyncRecordWriterFactory {
|
||||||
|
|
||||||
|
private final RecordWriterFactory recordWriterFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SyncRecordWriterFactoryImpl(RecordWriterFactory recordWriterFactory) {
|
||||||
|
this.recordWriterFactory = recordWriterFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncRecordWriter createRecordWriter(OutputStream out) {
|
||||||
|
RecordWriter writer = recordWriterFactory.createRecordWriter(out);
|
||||||
|
return new SyncRecordWriterImpl(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,81 +1,67 @@
|
|||||||
package org.briarproject.bramble.sync;
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.record.Record;
|
||||||
|
import org.briarproject.bramble.api.record.RecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.RecordTypes;
|
|
||||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.NotThreadSafe;
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||||
|
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||||
|
|
||||||
@NotThreadSafe
|
@NotThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class RecordWriterImpl implements RecordWriter {
|
class SyncRecordWriterImpl implements SyncRecordWriter {
|
||||||
|
|
||||||
private final OutputStream out;
|
private final RecordWriter writer;
|
||||||
private final byte[] header;
|
private final ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
||||||
private final ByteArrayOutputStream payload;
|
|
||||||
|
|
||||||
RecordWriterImpl(OutputStream out) {
|
SyncRecordWriterImpl(RecordWriter writer) {
|
||||||
this.out = out;
|
this.writer = writer;
|
||||||
header = new byte[RECORD_HEADER_LENGTH];
|
|
||||||
header[0] = PROTOCOL_VERSION;
|
|
||||||
payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeRecord(byte recordType) throws IOException {
|
private void writeRecord(byte recordType) throws IOException {
|
||||||
header[1] = recordType;
|
writer.writeRecord(new Record(PROTOCOL_VERSION, recordType,
|
||||||
ByteUtils.writeUint16(payload.size(), header, 2);
|
payload.toByteArray()));
|
||||||
out.write(header);
|
|
||||||
payload.writeTo(out);
|
|
||||||
payload.reset();
|
payload.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeAck(Ack a) throws IOException {
|
public void writeAck(Ack a) throws IOException {
|
||||||
if (payload.size() != 0) throw new IllegalStateException();
|
|
||||||
for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
|
for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
|
||||||
writeRecord(ACK);
|
writeRecord(ACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeMessage(byte[] raw) throws IOException {
|
public void writeMessage(byte[] raw) throws IOException {
|
||||||
header[1] = RecordTypes.MESSAGE;
|
writer.writeRecord(new Record(PROTOCOL_VERSION, MESSAGE, raw));
|
||||||
ByteUtils.writeUint16(raw.length, header, 2);
|
|
||||||
out.write(header);
|
|
||||||
out.write(raw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeOffer(Offer o) throws IOException {
|
public void writeOffer(Offer o) throws IOException {
|
||||||
if (payload.size() != 0) throw new IllegalStateException();
|
|
||||||
for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
|
for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
|
||||||
writeRecord(OFFER);
|
writeRecord(OFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeRequest(Request r) throws IOException {
|
public void writeRequest(Request r) throws IOException {
|
||||||
if (payload.size() != 0) throw new IllegalStateException();
|
|
||||||
for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
|
for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
|
||||||
writeRecord(REQUEST);
|
writeRecord(REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
out.flush();
|
writer.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,10 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.RecordReader;
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
|
||||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
@@ -28,14 +28,14 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
|||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final RecordReaderFactory recordReaderFactory;
|
private final SyncRecordReaderFactory recordReaderFactory;
|
||||||
private final RecordWriterFactory recordWriterFactory;
|
private final SyncRecordWriterFactory recordWriterFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SyncSessionFactoryImpl(DatabaseComponent db,
|
SyncSessionFactoryImpl(DatabaseComponent db,
|
||||||
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
||||||
Clock clock, RecordReaderFactory recordReaderFactory,
|
Clock clock, SyncRecordReaderFactory recordReaderFactory,
|
||||||
RecordWriterFactory recordWriterFactory) {
|
SyncRecordWriterFactory recordWriterFactory) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
@@ -46,14 +46,16 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SyncSession createIncomingSession(ContactId c, InputStream in) {
|
public SyncSession createIncomingSession(ContactId c, InputStream in) {
|
||||||
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
|
SyncRecordReader recordReader =
|
||||||
|
recordReaderFactory.createRecordReader(in);
|
||||||
return new IncomingSession(db, dbExecutor, eventBus, c, recordReader);
|
return new IncomingSession(db, dbExecutor, eventBus, c, recordReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SyncSession createSimplexOutgoingSession(ContactId c,
|
public SyncSession createSimplexOutgoingSession(ContactId c,
|
||||||
int maxLatency, OutputStream out) {
|
int maxLatency, OutputStream out) {
|
||||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
|
SyncRecordWriter recordWriter =
|
||||||
|
recordWriterFactory.createRecordWriter(out);
|
||||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
|
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
|
||||||
maxLatency, recordWriter);
|
maxLatency, recordWriter);
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,8 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
|||||||
@Override
|
@Override
|
||||||
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
|
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
|
||||||
int maxIdleTime, OutputStream out) {
|
int maxIdleTime, OutputStream out) {
|
||||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
|
SyncRecordWriter recordWriter =
|
||||||
|
recordWriterFactory.createRecordWriter(out);
|
||||||
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
|
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
|
||||||
maxLatency, maxIdleTime, recordWriter);
|
maxLatency, maxIdleTime, recordWriter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.MessageFactory;
|
|||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -51,8 +52,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor, validationExecutor;
|
private final Executor dbExecutor, validationExecutor;
|
||||||
private final MessageFactory messageFactory;
|
private final MessageFactory messageFactory;
|
||||||
private final Map<ClientId, MessageValidator> validators;
|
private final Map<ClientMajorVersion, MessageValidator> validators;
|
||||||
private final Map<ClientId, IncomingMessageHook> hooks;
|
private final Map<ClientMajorVersion, IncomingMessageHook> hooks;
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -81,14 +82,15 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerMessageValidator(ClientId c, MessageValidator v) {
|
public void registerMessageValidator(ClientId c, int majorVersion,
|
||||||
validators.put(c, v);
|
MessageValidator v) {
|
||||||
|
validators.put(new ClientMajorVersion(c, majorVersion), v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerIncomingMessageHook(ClientId c,
|
public void registerIncomingMessageHook(ClientId c, int majorVersion,
|
||||||
IncomingMessageHook hook) {
|
IncomingMessageHook hook) {
|
||||||
hooks.put(c, hook);
|
hooks.put(new ClientMajorVersion(c, majorVersion), hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateOutstandingMessagesAsync() {
|
private void validateOutstandingMessagesAsync() {
|
||||||
@@ -199,9 +201,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
Message m = messageFactory.createMessage(id, raw);
|
Message m = messageFactory.createMessage(id, raw);
|
||||||
Group g = db.getGroup(txn, m.getGroupId());
|
Group g = db.getGroup(txn, m.getGroupId());
|
||||||
ClientId c = g.getClientId();
|
ClientId c = g.getClientId();
|
||||||
|
int majorVersion = g.getMajorVersion();
|
||||||
Metadata meta =
|
Metadata meta =
|
||||||
db.getMessageMetadataForValidator(txn, id);
|
db.getMessageMetadataForValidator(txn, id);
|
||||||
DeliveryResult result = deliverMessage(txn, m, c, meta);
|
DeliveryResult result =
|
||||||
|
deliverMessage(txn, m, c, majorVersion, meta);
|
||||||
if (result.valid) {
|
if (result.valid) {
|
||||||
pending.addAll(getPendingDependents(txn, id));
|
pending.addAll(getPendingDependents(txn, id));
|
||||||
if (result.share) {
|
if (result.share) {
|
||||||
@@ -237,14 +241,16 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
|
|
||||||
@ValidationExecutor
|
@ValidationExecutor
|
||||||
private void validateMessage(Message m, Group g) {
|
private void validateMessage(Message m, Group g) {
|
||||||
MessageValidator v = validators.get(g.getClientId());
|
ClientMajorVersion cv =
|
||||||
|
new ClientMajorVersion(g.getClientId(), g.getMajorVersion());
|
||||||
|
MessageValidator v = validators.get(cv);
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING)) LOG.warning("No validator for " + cv);
|
||||||
LOG.warning("No validator for " + g.getClientId().getString());
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
MessageContext context = v.validateMessage(m, g);
|
MessageContext context = v.validateMessage(m, g);
|
||||||
storeMessageContextAsync(m, g.getClientId(), context);
|
storeMessageContextAsync(m, g.getClientId(),
|
||||||
|
g.getMajorVersion(), context);
|
||||||
} catch (InvalidMessageException e) {
|
} catch (InvalidMessageException e) {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.log(INFO, e.toString(), e);
|
LOG.log(INFO, e.toString(), e);
|
||||||
@@ -256,12 +262,13 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessageContextAsync(Message m, ClientId c,
|
private void storeMessageContextAsync(Message m, ClientId c,
|
||||||
MessageContext result) {
|
int majorVersion, MessageContext result) {
|
||||||
dbExecutor.execute(() -> storeMessageContext(m, c, result));
|
dbExecutor.execute(() ->
|
||||||
|
storeMessageContext(m, c, majorVersion, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void storeMessageContext(Message m, ClientId c,
|
private void storeMessageContext(Message m, ClientId c, int majorVersion,
|
||||||
MessageContext context) {
|
MessageContext context) {
|
||||||
try {
|
try {
|
||||||
MessageId id = m.getId();
|
MessageId id = m.getId();
|
||||||
@@ -292,7 +299,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
Metadata meta = context.getMetadata();
|
Metadata meta = context.getMetadata();
|
||||||
db.mergeMessageMetadata(txn, id, meta);
|
db.mergeMessageMetadata(txn, id, meta);
|
||||||
if (allDelivered) {
|
if (allDelivered) {
|
||||||
DeliveryResult result = deliverMessage(txn, m, c, meta);
|
DeliveryResult result =
|
||||||
|
deliverMessage(txn, m, c, majorVersion, meta);
|
||||||
if (result.valid) {
|
if (result.valid) {
|
||||||
pending = getPendingDependents(txn, id);
|
pending = getPendingDependents(txn, id);
|
||||||
if (result.share) {
|
if (result.share) {
|
||||||
@@ -324,10 +332,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private DeliveryResult deliverMessage(Transaction txn, Message m,
|
private DeliveryResult deliverMessage(Transaction txn, Message m,
|
||||||
ClientId c, Metadata meta) throws DbException {
|
ClientId c, int majorVersion, Metadata meta) throws DbException {
|
||||||
// Deliver the message to the client if it's registered a hook
|
// Deliver the message to the client if it's registered a hook
|
||||||
boolean shareMsg = false;
|
boolean shareMsg = false;
|
||||||
IncomingMessageHook hook = hooks.get(c);
|
ClientMajorVersion cv = new ClientMajorVersion(c, majorVersion);
|
||||||
|
IncomingMessageHook hook = hooks.get(cv);
|
||||||
if (hook != null) {
|
if (hook != null) {
|
||||||
try {
|
try {
|
||||||
shareMsg = hook.incomingMessage(txn, m, meta);
|
shareMsg = hook.incomingMessage(txn, m, meta);
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.Scheduler;
|
import org.briarproject.bramble.api.system.Scheduler;
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.RejectedExecutionHandler;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -25,7 +26,10 @@ public class SystemModule {
|
|||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
public SystemModule() {
|
public SystemModule() {
|
||||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
// Discard tasks that are submitted during shutdown
|
||||||
|
RejectedExecutionHandler policy =
|
||||||
|
new ScheduledThreadPoolExecutor.DiscardPolicy();
|
||||||
|
scheduler = new ScheduledThreadPoolExecutor(1, policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
import org.briarproject.bramble.api.transport.StreamContext;
|
import org.briarproject.bramble.api.transport.StreamContext;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -104,6 +105,67 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
m.addContact(txn, c, master, timestamp, alice);
|
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
|
@Override
|
||||||
public StreamContext getStreamContext(ContactId c, TransportId t)
|
public StreamContext getStreamContext(ContactId c, TransportId t)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -114,7 +176,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
StreamContext ctx = null;
|
StreamContext ctx;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
ctx = m.getStreamContext(txn, c);
|
ctx = m.getStreamContext(txn, c);
|
||||||
@@ -133,7 +195,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
|||||||
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
if (LOG.isLoggable(INFO)) LOG.info("No key manager for " + t);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
StreamContext ctx = null;
|
StreamContext ctx;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
ctx = m.getStreamContext(txn, tag);
|
ctx = m.getStreamContext(txn, tag);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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,17 +13,19 @@ class MutableOutgoingKeys {
|
|||||||
private final SecretKey tagKey, headerKey;
|
private final SecretKey tagKey, headerKey;
|
||||||
private final long rotationPeriod;
|
private final long rotationPeriod;
|
||||||
private long streamCounter;
|
private long streamCounter;
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
MutableOutgoingKeys(OutgoingKeys out) {
|
MutableOutgoingKeys(OutgoingKeys out) {
|
||||||
tagKey = out.getTagKey();
|
tagKey = out.getTagKey();
|
||||||
headerKey = out.getHeaderKey();
|
headerKey = out.getHeaderKey();
|
||||||
rotationPeriod = out.getRotationPeriod();
|
rotationPeriod = out.getRotationPeriod();
|
||||||
streamCounter = out.getStreamCounter();
|
streamCounter = out.getStreamCounter();
|
||||||
|
active = out.isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
OutgoingKeys snapshot() {
|
OutgoingKeys snapshot() {
|
||||||
return new OutgoingKeys(tagKey, headerKey, rotationPeriod,
|
return new OutgoingKeys(tagKey, headerKey, rotationPeriod,
|
||||||
streamCounter);
|
streamCounter, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
SecretKey getTagKey() {
|
SecretKey getTagKey() {
|
||||||
@@ -45,4 +47,12 @@ class MutableOutgoingKeys {
|
|||||||
void incrementStreamCounter() {
|
void incrementStreamCounter() {
|
||||||
streamCounter++;
|
streamCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
void activate() {
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
import org.briarproject.bramble.api.transport.StreamContext;
|
import org.briarproject.bramble.api.transport.StreamContext;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -17,8 +18,19 @@ interface TransportKeyManager {
|
|||||||
void addContact(Transaction txn, ContactId c, SecretKey master,
|
void addContact(Transaction txn, ContactId c, SecretKey master,
|
||||||
long timestamp, boolean alice) throws DbException;
|
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);
|
void removeContact(ContactId c);
|
||||||
|
|
||||||
|
boolean canSendOutgoingStreams(ContactId c);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
StreamContext getStreamContext(Transaction txn, ContactId c)
|
StreamContext getStreamContext(Transaction txn, ContactId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|||||||
@@ -11,19 +11,24 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.Scheduler;
|
import org.briarproject.bramble.api.system.Scheduler;
|
||||||
|
import 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.StreamContext;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
import org.briarproject.bramble.transport.ReorderingWindow.Change;
|
import org.briarproject.bramble.transport.ReorderingWindow.Change;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
@@ -47,12 +52,13 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
private final long rotationPeriodLength;
|
private final long rotationPeriodLength;
|
||||||
private final ReentrantLock lock;
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
// The following are locking: lock
|
// The following are locking: lock
|
||||||
private final Map<Bytes, TagContext> inContexts;
|
private final Map<KeySetId, MutableKeySet> keys = new HashMap<>();
|
||||||
private final Map<ContactId, MutableOutgoingKeys> outContexts;
|
private final Map<Bytes, TagContext> inContexts = new HashMap<>();
|
||||||
private final Map<ContactId, MutableTransportKeys> keys;
|
private final Map<ContactId, MutableKeySet> outContexts = new HashMap<>();
|
||||||
|
|
||||||
TransportKeyManagerImpl(DatabaseComponent db,
|
TransportKeyManagerImpl(DatabaseComponent db,
|
||||||
TransportCrypto transportCrypto, Executor dbExecutor,
|
TransportCrypto transportCrypto, Executor dbExecutor,
|
||||||
@@ -65,20 +71,16 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.transportId = transportId;
|
this.transportId = transportId;
|
||||||
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
rotationPeriodLength = maxLatency + MAX_CLOCK_DIFFERENCE;
|
||||||
lock = new ReentrantLock();
|
|
||||||
inContexts = new HashMap<>();
|
|
||||||
outContexts = new HashMap<>();
|
|
||||||
keys = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Transaction txn) throws DbException {
|
public void start(Transaction txn) throws DbException {
|
||||||
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
// Load the transport keys from the DB
|
// Load the transport keys from the DB
|
||||||
Map<ContactId, TransportKeys> loaded =
|
Collection<KeySet> loaded = db.getTransportKeys(txn, transportId);
|
||||||
db.getTransportKeys(txn, transportId);
|
|
||||||
// Rotate the keys to the current rotation period
|
// Rotate the keys to the current rotation period
|
||||||
RotationResult rotationResult = rotateKeys(loaded, now);
|
RotationResult rotationResult = rotateKeys(loaded, now);
|
||||||
// Initialise mutable state for all contacts
|
// Initialise mutable state for all contacts
|
||||||
@@ -93,41 +95,48 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
scheduleKeyRotation(now);
|
scheduleKeyRotation(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RotationResult rotateKeys(Map<ContactId, TransportKeys> keys,
|
private RotationResult rotateKeys(Collection<KeySet> keys, long now) {
|
||||||
long now) {
|
|
||||||
RotationResult rotationResult = new RotationResult();
|
RotationResult rotationResult = new RotationResult();
|
||||||
long rotationPeriod = now / rotationPeriodLength;
|
long rotationPeriod = now / rotationPeriodLength;
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
for (KeySet ks : keys) {
|
||||||
ContactId c = e.getKey();
|
TransportKeys k = ks.getTransportKeys();
|
||||||
TransportKeys k = e.getValue();
|
|
||||||
TransportKeys k1 =
|
TransportKeys k1 =
|
||||||
transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||||
|
KeySet ks1 = new KeySet(ks.getKeySetId(), ks.getContactId(), k1);
|
||||||
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
||||||
rotationResult.rotated.put(c, k1);
|
rotationResult.rotated.add(ks1);
|
||||||
rotationResult.current.put(c, k1);
|
rotationResult.current.add(ks1);
|
||||||
}
|
}
|
||||||
return rotationResult;
|
return rotationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: lock
|
// Locking: lock
|
||||||
private void addKeys(Map<ContactId, TransportKeys> m) {
|
private void addKeys(Collection<KeySet> keys) {
|
||||||
for (Entry<ContactId, TransportKeys> e : m.entrySet())
|
for (KeySet ks : keys) {
|
||||||
addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
|
addKeys(ks.getKeySetId(), ks.getContactId(),
|
||||||
|
new MutableTransportKeys(ks.getTransportKeys()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: lock
|
// Locking: lock
|
||||||
private void addKeys(ContactId c, MutableTransportKeys m) {
|
private void addKeys(KeySetId keySetId, @Nullable ContactId contactId,
|
||||||
encodeTags(c, m.getPreviousIncomingKeys());
|
MutableTransportKeys m) {
|
||||||
encodeTags(c, m.getCurrentIncomingKeys());
|
MutableKeySet ks = new MutableKeySet(keySetId, contactId, m);
|
||||||
encodeTags(c, m.getNextIncomingKeys());
|
keys.put(keySetId, ks);
|
||||||
outContexts.put(c, m.getCurrentOutgoingKeys());
|
if (contactId != null) {
|
||||||
keys.put(c, m);
|
encodeTags(keySetId, contactId, m.getPreviousIncomingKeys());
|
||||||
|
encodeTags(keySetId, contactId, m.getCurrentIncomingKeys());
|
||||||
|
encodeTags(keySetId, contactId, m.getNextIncomingKeys());
|
||||||
|
considerReplacingOutgoingKeys(ks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: lock
|
// Locking: lock
|
||||||
private void encodeTags(ContactId c, MutableIncomingKeys inKeys) {
|
private void encodeTags(KeySetId keySetId, ContactId contactId,
|
||||||
|
MutableIncomingKeys inKeys) {
|
||||||
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
||||||
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
TagContext tagCtx =
|
||||||
|
new TagContext(keySetId, contactId, inKeys, streamNumber);
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
||||||
streamNumber);
|
streamNumber);
|
||||||
@@ -135,6 +144,17 @@ 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) {
|
private void scheduleKeyRotation(long now) {
|
||||||
long delay = rotationPeriodLength - now % rotationPeriodLength;
|
long delay = rotationPeriodLength - now % rotationPeriodLength;
|
||||||
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS);
|
scheduler.schedule((Runnable) this::rotateKeys, delay, MILLISECONDS);
|
||||||
@@ -159,20 +179,82 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
@Override
|
@Override
|
||||||
public void addContact(Transaction txn, ContactId c, SecretKey master,
|
public void addContact(Transaction txn, ContactId c, SecretKey master,
|
||||||
long timestamp, boolean alice) throws DbException {
|
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();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
// Work out what rotation period the timestamp belongs to
|
// Work out what rotation period the timestamp belongs to
|
||||||
long rotationPeriod = timestamp / rotationPeriodLength;
|
long rotationPeriod = timestamp / rotationPeriodLength;
|
||||||
// Derive the transport keys
|
// Derive the transport keys
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
master, rotationPeriod, alice);
|
master, rotationPeriod, alice, active);
|
||||||
// Rotate the keys to the current rotation period if necessary
|
// Rotate the keys to the current rotation period if necessary
|
||||||
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
||||||
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||||
// Initialise mutable state for the contact
|
|
||||||
addKeys(c, new MutableTransportKeys(k));
|
|
||||||
// Write the keys back to the DB
|
// Write the keys back to the DB
|
||||||
db.addTransportKeys(txn, c, k);
|
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);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -183,12 +265,29 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
// Remove mutable state for the contact
|
// Remove mutable state for the contact
|
||||||
Iterator<Entry<Bytes, TagContext>> it =
|
Iterator<TagContext> it = inContexts.values().iterator();
|
||||||
inContexts.entrySet().iterator();
|
while (it.hasNext()) if (it.next().contactId.equals(c)) it.remove();
|
||||||
while (it.hasNext())
|
|
||||||
if (it.next().getValue().contactId.equals(c)) it.remove();
|
|
||||||
outContexts.remove(c);
|
outContexts.remove(c);
|
||||||
keys.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;
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -200,8 +299,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
// Look up the outgoing keys for the contact
|
// Look up the outgoing keys for the contact
|
||||||
MutableOutgoingKeys outKeys = outContexts.get(c);
|
MutableKeySet ks = outContexts.get(c);
|
||||||
if (outKeys == null) return null;
|
if (ks == null) return null;
|
||||||
|
MutableOutgoingKeys outKeys =
|
||||||
|
ks.getTransportKeys().getCurrentOutgoingKeys();
|
||||||
|
if (!outKeys.isActive()) throw new AssertionError();
|
||||||
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
|
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
|
||||||
// Create a stream context
|
// Create a stream context
|
||||||
StreamContext ctx = new StreamContext(c, transportId,
|
StreamContext ctx = new StreamContext(c, transportId,
|
||||||
@@ -209,8 +311,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
outKeys.getStreamCounter());
|
outKeys.getStreamCounter());
|
||||||
// Increment the stream counter and write it back to the DB
|
// Increment the stream counter and write it back to the DB
|
||||||
outKeys.incrementStreamCounter();
|
outKeys.incrementStreamCounter();
|
||||||
db.incrementStreamCounter(txn, c, transportId,
|
db.incrementStreamCounter(txn, transportId, ks.getKeySetId());
|
||||||
outKeys.getRotationPeriod());
|
|
||||||
return ctx;
|
return ctx;
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
@@ -238,8 +339,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
byte[] addTag = new byte[TAG_LENGTH];
|
byte[] addTag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
|
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
|
||||||
PROTOCOL_VERSION, streamNumber);
|
PROTOCOL_VERSION, streamNumber);
|
||||||
inContexts.put(new Bytes(addTag), new TagContext(
|
TagContext tagCtx1 = new TagContext(tagCtx.keySetId,
|
||||||
tagCtx.contactId, inKeys, streamNumber));
|
tagCtx.contactId, inKeys, streamNumber);
|
||||||
|
inContexts.put(new Bytes(addTag), tagCtx1);
|
||||||
}
|
}
|
||||||
// Remove tags for any stream numbers removed from the window
|
// Remove tags for any stream numbers removed from the window
|
||||||
for (long streamNumber : change.getRemoved()) {
|
for (long streamNumber : change.getRemoved()) {
|
||||||
@@ -250,9 +352,19 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
inContexts.remove(new Bytes(removeTag));
|
inContexts.remove(new Bytes(removeTag));
|
||||||
}
|
}
|
||||||
// Write the window back to the DB
|
// Write the window back to the DB
|
||||||
db.setReorderingWindow(txn, tagCtx.contactId, transportId,
|
db.setReorderingWindow(txn, tagCtx.keySetId, transportId,
|
||||||
inKeys.getRotationPeriod(), window.getBase(),
|
inKeys.getRotationPeriod(), window.getBase(),
|
||||||
window.getBitmap());
|
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;
|
return ctx;
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
@@ -264,9 +376,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
// Rotate the keys to the current rotation period
|
// Rotate the keys to the current rotation period
|
||||||
Map<ContactId, TransportKeys> snapshot = new HashMap<>();
|
Collection<KeySet> snapshot = new ArrayList<>(keys.size());
|
||||||
for (Entry<ContactId, MutableTransportKeys> e : keys.entrySet())
|
for (MutableKeySet ks : keys.values()) {
|
||||||
snapshot.put(e.getKey(), e.getValue().snapshot());
|
snapshot.add(new KeySet(ks.getKeySetId(), ks.getContactId(),
|
||||||
|
ks.getTransportKeys().snapshot()));
|
||||||
|
}
|
||||||
RotationResult rotationResult = rotateKeys(snapshot, now);
|
RotationResult rotationResult = rotateKeys(snapshot, now);
|
||||||
// Rebuild the mutable state for all contacts
|
// Rebuild the mutable state for all contacts
|
||||||
inContexts.clear();
|
inContexts.clear();
|
||||||
@@ -285,12 +399,14 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
|
|
||||||
private static class TagContext {
|
private static class TagContext {
|
||||||
|
|
||||||
|
private final KeySetId keySetId;
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final MutableIncomingKeys inKeys;
|
private final MutableIncomingKeys inKeys;
|
||||||
private final long streamNumber;
|
private final long streamNumber;
|
||||||
|
|
||||||
private TagContext(ContactId contactId, MutableIncomingKeys inKeys,
|
private TagContext(KeySetId keySetId, ContactId contactId,
|
||||||
long streamNumber) {
|
MutableIncomingKeys inKeys, long streamNumber) {
|
||||||
|
this.keySetId = keySetId;
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.inKeys = inKeys;
|
this.inKeys = inKeys;
|
||||||
this.streamNumber = streamNumber;
|
this.streamNumber = streamNumber;
|
||||||
@@ -299,11 +415,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
|
|
||||||
private static class RotationResult {
|
private static class RotationResult {
|
||||||
|
|
||||||
private final Map<ContactId, TransportKeys> current, rotated;
|
private final Collection<KeySet> current = new ArrayList<>();
|
||||||
|
private final Collection<KeySet> rotated = new ArrayList<>();
|
||||||
private RotationResult() {
|
|
||||||
current = new HashMap<>();
|
|
||||||
rotated = new HashMap<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.briarproject.bramble.versioning;
|
||||||
|
|
||||||
|
interface ClientVersioningConstants {
|
||||||
|
|
||||||
|
// Metadata keys
|
||||||
|
String MSG_KEY_UPDATE_VERSION = "version";
|
||||||
|
String MSG_KEY_LOCAL = "local";
|
||||||
|
String GROUP_KEY_CONTACT_ID = "contactId";
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,622 @@
|
|||||||
|
package org.briarproject.bramble.versioning;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
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.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.Client;
|
||||||
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.Group.Visibility;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientMajorVersion;
|
||||||
|
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
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.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||||
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
|
||||||
|
Service, ContactHook, IncomingMessageHook {
|
||||||
|
|
||||||
|
private final DatabaseComponent db;
|
||||||
|
private final ClientHelper clientHelper;
|
||||||
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
|
private final Clock clock;
|
||||||
|
private final Group localGroup;
|
||||||
|
|
||||||
|
private final List<ClientVersion> clients = new CopyOnWriteArrayList<>();
|
||||||
|
private final Map<ClientMajorVersion, ClientVersioningHook> hooks =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ClientVersioningManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||||
|
ContactGroupFactory contactGroupFactory, Clock clock) {
|
||||||
|
this.db = db;
|
||||||
|
this.clientHelper = clientHelper;
|
||||||
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
|
this.clock = clock;
|
||||||
|
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerClient(ClientId clientId, int majorVersion,
|
||||||
|
int minorVersion, ClientVersioningHook hook) {
|
||||||
|
ClientMajorVersion cv = new ClientMajorVersion(clientId, majorVersion);
|
||||||
|
clients.add(new ClientVersion(cv, minorVersion));
|
||||||
|
hooks.put(cv, hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Visibility getClientVisibility(Transaction txn, ContactId contactId,
|
||||||
|
ClientId clientId, int majorVersion) throws DbException {
|
||||||
|
try {
|
||||||
|
Contact contact = db.getContact(txn, contactId);
|
||||||
|
Group g = getContactGroup(contact);
|
||||||
|
// Contact may be in the process of being added or removed, so
|
||||||
|
// contact group may not exist
|
||||||
|
if (!db.containsGroup(txn, g.getId())) return INVISIBLE;
|
||||||
|
LatestUpdates latest = findLatestUpdates(txn, g.getId());
|
||||||
|
if (latest.local == null) throw new DbException();
|
||||||
|
if (latest.remote == null) return INVISIBLE;
|
||||||
|
Update localUpdate = loadUpdate(txn, latest.local.messageId);
|
||||||
|
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
|
||||||
|
Map<ClientMajorVersion, Visibility> visibilities =
|
||||||
|
getVisibilities(localUpdate.states, remoteUpdate.states);
|
||||||
|
ClientMajorVersion cv =
|
||||||
|
new ClientMajorVersion(clientId, majorVersion);
|
||||||
|
Visibility v = visibilities.get(cv);
|
||||||
|
return v == null ? INVISIBLE : v;
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createLocalState(Transaction txn) throws DbException {
|
||||||
|
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||||
|
db.addGroup(txn, localGroup);
|
||||||
|
// Set things up for any pre-existing contacts
|
||||||
|
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startService() throws ServiceException {
|
||||||
|
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||||
|
Collections.sort(versions);
|
||||||
|
try {
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
|
try {
|
||||||
|
if (updateClientVersions(txn, versions)) {
|
||||||
|
for (Contact c : db.getContacts(txn))
|
||||||
|
clientVersionsUpdated(txn, c, versions);
|
||||||
|
}
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(txn);
|
||||||
|
}
|
||||||
|
} catch (DbException e) {
|
||||||
|
throw new ServiceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopService() throws ServiceException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addingContact(Transaction txn, Contact c) throws DbException {
|
||||||
|
// Create a group and share it with the contact
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
db.addGroup(txn, g);
|
||||||
|
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
|
||||||
|
// Attach the contact ID to the group
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
|
||||||
|
try {
|
||||||
|
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
// Create and store the first local update
|
||||||
|
List<ClientVersion> versions = new ArrayList<>(clients);
|
||||||
|
Collections.sort(versions);
|
||||||
|
storeFirstUpdate(txn, g.getId(), versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||||
|
db.removeGroup(txn, getContactGroup(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
|
||||||
|
throws DbException, InvalidMessageException {
|
||||||
|
try {
|
||||||
|
// Parse the new remote update
|
||||||
|
Update newRemoteUpdate = parseUpdate(clientHelper.toList(m));
|
||||||
|
List<ClientState> newRemoteStates = newRemoteUpdate.states;
|
||||||
|
long newRemoteUpdateVersion = newRemoteUpdate.updateVersion;
|
||||||
|
// Find the latest local and remote updates, if any
|
||||||
|
LatestUpdates latest = findLatestUpdates(txn, m.getGroupId());
|
||||||
|
// If this update is obsolete, delete it and return
|
||||||
|
if (latest.remote != null
|
||||||
|
&& latest.remote.updateVersion > newRemoteUpdateVersion) {
|
||||||
|
db.deleteMessage(txn, m.getId());
|
||||||
|
db.deleteMessageMetadata(txn, m.getId());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Load and parse the latest local update
|
||||||
|
if (latest.local == null) throw new DbException();
|
||||||
|
Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId);
|
||||||
|
List<ClientState> oldLocalStates = oldLocalUpdate.states;
|
||||||
|
long oldLocalUpdateVersion = oldLocalUpdate.updateVersion;
|
||||||
|
// Load and parse the previous remote update, if any
|
||||||
|
List<ClientState> oldRemoteStates;
|
||||||
|
if (latest.remote == null) {
|
||||||
|
oldRemoteStates = emptyList();
|
||||||
|
} else {
|
||||||
|
oldRemoteStates =
|
||||||
|
loadUpdate(txn, latest.remote.messageId).states;
|
||||||
|
// Delete the previous remote update
|
||||||
|
db.deleteMessage(txn, latest.remote.messageId);
|
||||||
|
db.deleteMessageMetadata(txn, latest.remote.messageId);
|
||||||
|
}
|
||||||
|
// Update the local states from the remote states if necessary
|
||||||
|
List<ClientState> newLocalStates = updateStatesFromRemoteStates(
|
||||||
|
oldLocalStates, newRemoteStates);
|
||||||
|
if (!oldLocalStates.equals(newLocalStates)) {
|
||||||
|
// Delete the latest local update
|
||||||
|
db.deleteMessage(txn, latest.local.messageId);
|
||||||
|
db.deleteMessageMetadata(txn, latest.local.messageId);
|
||||||
|
// Store a new local update
|
||||||
|
storeUpdate(txn, m.getGroupId(), newLocalStates,
|
||||||
|
oldLocalUpdateVersion + 1);
|
||||||
|
}
|
||||||
|
// Calculate the old and new client visibilities
|
||||||
|
Map<ClientMajorVersion, Visibility> before =
|
||||||
|
getVisibilities(oldLocalStates, oldRemoteStates);
|
||||||
|
Map<ClientMajorVersion, Visibility> after =
|
||||||
|
getVisibilities(newLocalStates, newRemoteStates);
|
||||||
|
// Call hooks for any visibilities that have changed
|
||||||
|
if (!before.equals(after)) {
|
||||||
|
Contact c = getContact(txn, m.getGroupId());
|
||||||
|
callVisibilityHooks(txn, c, before, after);
|
||||||
|
}
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeClientVersions(Transaction txn,
|
||||||
|
List<ClientVersion> versions) throws DbException {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
BdfList body = encodeClientVersions(versions);
|
||||||
|
try {
|
||||||
|
Message m = clientHelper.createMessage(localGroup.getId(), now,
|
||||||
|
body);
|
||||||
|
db.addLocalMessage(txn, m, new Metadata(), false);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfList encodeClientVersions(List<ClientVersion> versions) {
|
||||||
|
BdfList encoded = new BdfList();
|
||||||
|
for (ClientVersion cv : versions) encoded.add(encodeClientVersion(cv));
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfList encodeClientVersion(ClientVersion cv) {
|
||||||
|
return BdfList.of(cv.majorVersion.getClientId().getString(),
|
||||||
|
cv.majorVersion.getMajorVersion(), cv.minorVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the local client versions and returns true if an update needs to
|
||||||
|
* be sent to contacts.
|
||||||
|
*/
|
||||||
|
private boolean updateClientVersions(Transaction txn,
|
||||||
|
List<ClientVersion> newVersions) throws DbException {
|
||||||
|
Collection<MessageId> ids = db.getMessageIds(txn, localGroup.getId());
|
||||||
|
if (ids.isEmpty()) {
|
||||||
|
storeClientVersions(txn, newVersions);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ids.size() != 1) throw new DbException();
|
||||||
|
MessageId m = ids.iterator().next();
|
||||||
|
List<ClientVersion> oldVersions = loadClientVersions(txn, m);
|
||||||
|
if (oldVersions.equals(newVersions)) return false;
|
||||||
|
db.removeMessage(txn, m);
|
||||||
|
storeClientVersions(txn, newVersions);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ClientVersion> loadClientVersions(Transaction txn,
|
||||||
|
MessageId m) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfList body = clientHelper.getMessageAsList(txn, m);
|
||||||
|
if (body == null) throw new DbException();
|
||||||
|
return parseClientVersions(body);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ClientVersion> parseClientVersions(BdfList body)
|
||||||
|
throws FormatException {
|
||||||
|
int size = body.size();
|
||||||
|
List<ClientVersion> parsed = new ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
BdfList cv = body.getList(i);
|
||||||
|
ClientId clientId = new ClientId(cv.getString(0));
|
||||||
|
int majorVersion = cv.getLong(1).intValue();
|
||||||
|
int minorVersion = cv.getLong(2).intValue();
|
||||||
|
parsed.add(new ClientVersion(clientId, majorVersion,
|
||||||
|
minorVersion));
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clientVersionsUpdated(Transaction txn, Contact c,
|
||||||
|
List<ClientVersion> versions) throws DbException {
|
||||||
|
try {
|
||||||
|
// Find the latest local and remote updates
|
||||||
|
Group g = getContactGroup(c);
|
||||||
|
LatestUpdates latest = findLatestUpdates(txn, g.getId());
|
||||||
|
// Load and parse the latest local update
|
||||||
|
if (latest.local == null) throw new DbException();
|
||||||
|
Update oldLocalUpdate = loadUpdate(txn, latest.local.messageId);
|
||||||
|
List<ClientState> oldLocalStates = oldLocalUpdate.states;
|
||||||
|
long oldLocalUpdateVersion = oldLocalUpdate.updateVersion;
|
||||||
|
// Load and parse the latest remote update, if any
|
||||||
|
List<ClientState> remoteStates;
|
||||||
|
if (latest.remote == null) remoteStates = emptyList();
|
||||||
|
else remoteStates = loadUpdate(txn, latest.remote.messageId).states;
|
||||||
|
// Update the local states if necessary
|
||||||
|
List<ClientState> newLocalStates =
|
||||||
|
updateStatesFromLocalVersions(oldLocalStates, versions);
|
||||||
|
newLocalStates = updateStatesFromRemoteStates(newLocalStates,
|
||||||
|
remoteStates);
|
||||||
|
if (!oldLocalStates.equals(newLocalStates)) {
|
||||||
|
// Delete the latest local update
|
||||||
|
db.deleteMessage(txn, latest.local.messageId);
|
||||||
|
db.deleteMessageMetadata(txn, latest.local.messageId);
|
||||||
|
// Store a new local update
|
||||||
|
storeUpdate(txn, g.getId(), newLocalStates,
|
||||||
|
oldLocalUpdateVersion + 1);
|
||||||
|
}
|
||||||
|
// Calculate the old and new client visibilities
|
||||||
|
Map<ClientMajorVersion, Visibility> before =
|
||||||
|
getVisibilities(oldLocalStates, remoteStates);
|
||||||
|
Map<ClientMajorVersion, Visibility> after =
|
||||||
|
getVisibilities(newLocalStates, remoteStates);
|
||||||
|
// Call hooks for any visibilities that have changed
|
||||||
|
callVisibilityHooks(txn, c, before, after);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Group getContactGroup(Contact c) {
|
||||||
|
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||||
|
MAJOR_VERSION, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LatestUpdates findLatestUpdates(Transaction txn, GroupId g)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
|
LatestUpdate local = null, remote = null;
|
||||||
|
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||||
|
BdfDictionary meta = e.getValue();
|
||||||
|
long updateVersion = meta.getLong(MSG_KEY_UPDATE_VERSION);
|
||||||
|
if (meta.getBoolean(MSG_KEY_LOCAL))
|
||||||
|
local = new LatestUpdate(e.getKey(), updateVersion);
|
||||||
|
else remote = new LatestUpdate(e.getKey(), updateVersion);
|
||||||
|
}
|
||||||
|
return new LatestUpdates(local, remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Update loadUpdate(Transaction txn, MessageId m) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfList body = clientHelper.getMessageAsList(txn, m);
|
||||||
|
if (body == null) throw new DbException();
|
||||||
|
return parseUpdate(body);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Update parseUpdate(BdfList body) throws FormatException {
|
||||||
|
List<ClientState> states = parseClientStates(body);
|
||||||
|
long updateVersion = parseUpdateVersion(body);
|
||||||
|
return new Update(states, updateVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ClientState> parseClientStates(BdfList body)
|
||||||
|
throws FormatException {
|
||||||
|
// Client states, update version
|
||||||
|
BdfList states = body.getList(0);
|
||||||
|
int size = states.size();
|
||||||
|
List<ClientState> parsed = new ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
parsed.add(parseClientState(states.getList(i)));
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientState parseClientState(BdfList clientState)
|
||||||
|
throws FormatException {
|
||||||
|
// Client ID, major version, minor version, active
|
||||||
|
ClientId clientId = new ClientId(clientState.getString(0));
|
||||||
|
int majorVersion = clientState.getLong(1).intValue();
|
||||||
|
int minorVersion = clientState.getLong(2).intValue();
|
||||||
|
boolean active = clientState.getBoolean(3);
|
||||||
|
return new ClientState(clientId, majorVersion, minorVersion, active);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long parseUpdateVersion(BdfList body) throws FormatException {
|
||||||
|
// Client states, update version
|
||||||
|
return body.getLong(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ClientState> updateStatesFromLocalVersions(
|
||||||
|
List<ClientState> oldStates, List<ClientVersion> newVersions) {
|
||||||
|
Map<ClientMajorVersion, ClientState> oldMap = new HashMap<>();
|
||||||
|
for (ClientState cs : oldStates) oldMap.put(cs.majorVersion, cs);
|
||||||
|
List<ClientState> newStates = new ArrayList<>(newVersions.size());
|
||||||
|
for (ClientVersion newVersion : newVersions) {
|
||||||
|
ClientState oldState = oldMap.get(newVersion.majorVersion);
|
||||||
|
boolean active = oldState != null && oldState.active;
|
||||||
|
newStates.add(new ClientState(newVersion.majorVersion,
|
||||||
|
newVersion.minorVersion, active));
|
||||||
|
}
|
||||||
|
return newStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeUpdate(Transaction txn, GroupId g,
|
||||||
|
List<ClientState> states, long updateVersion) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfList body = encodeUpdate(states, updateVersion);
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
Message m = clientHelper.createMessage(g, now, body);
|
||||||
|
BdfDictionary meta = new BdfDictionary();
|
||||||
|
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
|
||||||
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
|
clientHelper.addLocalMessage(txn, m, meta, true);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfList encodeUpdate(List<ClientState> states, long updateVersion) {
|
||||||
|
BdfList encoded = new BdfList();
|
||||||
|
for (ClientState cs : states) encoded.add(encodeClientState(cs));
|
||||||
|
return BdfList.of(encoded, updateVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BdfList encodeClientState(ClientState cs) {
|
||||||
|
return BdfList.of(cs.majorVersion.getClientId().getString(),
|
||||||
|
cs.majorVersion.getMajorVersion(), cs.minorVersion, cs.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<ClientMajorVersion, Visibility> getVisibilities(
|
||||||
|
List<ClientState> localStates, List<ClientState> remoteStates) {
|
||||||
|
Map<ClientMajorVersion, ClientState> remoteMap = new HashMap<>();
|
||||||
|
for (ClientState cs : remoteStates) remoteMap.put(cs.majorVersion, cs);
|
||||||
|
Map<ClientMajorVersion, Visibility> visibilities = new HashMap<>();
|
||||||
|
for (ClientState local : localStates) {
|
||||||
|
ClientState remote = remoteMap.get(local.majorVersion);
|
||||||
|
if (remote == null) visibilities.put(local.majorVersion, INVISIBLE);
|
||||||
|
else if (remote.active)
|
||||||
|
visibilities.put(local.majorVersion, SHARED);
|
||||||
|
else visibilities.put(local.majorVersion, VISIBLE);
|
||||||
|
}
|
||||||
|
return visibilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callVisibilityHooks(Transaction txn, Contact c,
|
||||||
|
Map<ClientMajorVersion, Visibility> before,
|
||||||
|
Map<ClientMajorVersion, Visibility> after) throws DbException {
|
||||||
|
Set<ClientMajorVersion> keys = new TreeSet<>();
|
||||||
|
keys.addAll(before.keySet());
|
||||||
|
keys.addAll(after.keySet());
|
||||||
|
for (ClientMajorVersion cv : keys) {
|
||||||
|
Visibility vBefore = before.get(cv), vAfter = after.get(cv);
|
||||||
|
if (vAfter == null) {
|
||||||
|
callVisibilityHook(txn, cv, c, INVISIBLE);
|
||||||
|
} else if (vBefore == null || !vBefore.equals(vAfter)) {
|
||||||
|
callVisibilityHook(txn, cv, c, vAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callVisibilityHook(Transaction txn, ClientMajorVersion cv,
|
||||||
|
Contact c, Visibility v) throws DbException {
|
||||||
|
ClientVersioningHook hook = hooks.get(cv);
|
||||||
|
if (hook != null) hook.onClientVisibilityChanging(txn, c, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeFirstUpdate(Transaction txn, GroupId g,
|
||||||
|
List<ClientVersion> versions) throws DbException {
|
||||||
|
List<ClientState> states = new ArrayList<>(versions.size());
|
||||||
|
for (ClientVersion cv : versions) {
|
||||||
|
states.add(new ClientState(cv.majorVersion, cv.minorVersion,
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
storeUpdate(txn, g, states, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Contact getContact(Transaction txn, GroupId g) throws DbException {
|
||||||
|
try {
|
||||||
|
BdfDictionary meta =
|
||||||
|
clientHelper.getGroupMetadataAsDictionary(txn, g);
|
||||||
|
int id = meta.getLong(GROUP_KEY_CONTACT_ID).intValue();
|
||||||
|
return db.getContact(txn, new ContactId(id));
|
||||||
|
} catch (FormatException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ClientState> updateStatesFromRemoteStates(
|
||||||
|
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
|
||||||
|
Set<ClientMajorVersion> remoteSet = new HashSet<>();
|
||||||
|
for (ClientState cs : remoteStates) remoteSet.add(cs.majorVersion);
|
||||||
|
List<ClientState> newLocalStates =
|
||||||
|
new ArrayList<>(oldLocalStates.size());
|
||||||
|
for (ClientState oldState : oldLocalStates) {
|
||||||
|
boolean active = remoteSet.contains(oldState.majorVersion);
|
||||||
|
newLocalStates.add(new ClientState(oldState.majorVersion,
|
||||||
|
oldState.minorVersion, active));
|
||||||
|
}
|
||||||
|
return newLocalStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Update {
|
||||||
|
|
||||||
|
private final List<ClientState> states;
|
||||||
|
private final long updateVersion;
|
||||||
|
|
||||||
|
private Update(List<ClientState> states, long updateVersion) {
|
||||||
|
this.states = states;
|
||||||
|
this.updateVersion = updateVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LatestUpdate {
|
||||||
|
|
||||||
|
private final MessageId messageId;
|
||||||
|
private final long updateVersion;
|
||||||
|
|
||||||
|
private LatestUpdate(MessageId messageId, long updateVersion) {
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.updateVersion = updateVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LatestUpdates {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final LatestUpdate local, remote;
|
||||||
|
|
||||||
|
private LatestUpdates(@Nullable LatestUpdate local,
|
||||||
|
@Nullable LatestUpdate remote) {
|
||||||
|
this.local = local;
|
||||||
|
this.remote = remote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ClientVersion implements Comparable<ClientVersion> {
|
||||||
|
|
||||||
|
private final ClientMajorVersion majorVersion;
|
||||||
|
private final int minorVersion;
|
||||||
|
|
||||||
|
private ClientVersion(ClientMajorVersion majorVersion,
|
||||||
|
int minorVersion) {
|
||||||
|
this.majorVersion = majorVersion;
|
||||||
|
this.minorVersion = minorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientVersion(ClientId clientId, int majorVersion,
|
||||||
|
int minorVersion) {
|
||||||
|
this(new ClientMajorVersion(clientId, majorVersion), minorVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof ClientVersion) {
|
||||||
|
ClientVersion cv = (ClientVersion) o;
|
||||||
|
return majorVersion.equals(cv.majorVersion)
|
||||||
|
&& minorVersion == cv.minorVersion;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return majorVersion.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(ClientVersion cv) {
|
||||||
|
int compare = majorVersion.compareTo(cv.majorVersion);
|
||||||
|
if (compare != 0) return compare;
|
||||||
|
return minorVersion - cv.minorVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ClientState {
|
||||||
|
|
||||||
|
private final ClientMajorVersion majorVersion;
|
||||||
|
private final int minorVersion;
|
||||||
|
private final boolean active;
|
||||||
|
|
||||||
|
private ClientState(ClientMajorVersion majorVersion, int minorVersion,
|
||||||
|
boolean active) {
|
||||||
|
this.majorVersion = majorVersion;
|
||||||
|
this.minorVersion = minorVersion;
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientState(ClientId clientId, int majorVersion,
|
||||||
|
int minorVersion, boolean active) {
|
||||||
|
this(new ClientMajorVersion(clientId, majorVersion), minorVersion,
|
||||||
|
active);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof ClientState) {
|
||||||
|
ClientState cs = (ClientState) o;
|
||||||
|
return majorVersion.equals(cs.majorVersion)
|
||||||
|
&& minorVersion == cs.minorVersion
|
||||||
|
&& active == cs.active;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return majorVersion.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user